From 4147a1ce20295ef5ba60ac64d11d3d4a243cb896 Mon Sep 17 00:00:00 2001
From: skomoroh <skomoroh@39dd0a4e-7c14-0410-91b3-c4f2d318f732>
Date: Fri, 4 May 2007 21:17:52 +0000
Subject: [PATCH] + added solitaire wizard to Tk-binding * improved solitaire
 wizard

git-svn-id: https://pysolfc.svn.sourceforge.net/svnroot/pysolfc/PySolFC/trunk@158 39dd0a4e-7c14-0410-91b3-c4f2d318f732
---
 pysollib/customgame.py        | 140 +++++++++++++++++++++++++---------
 pysollib/game.py              |  21 +++++
 pysollib/games/spider.py      |  30 +-------
 pysollib/stack.py             |  64 +++++++++++++++-
 pysollib/tile/wizarddialog.py |  14 ++--
 pysollib/tk/menubar.py        |  48 ++++++++++++
 pysollib/tk/tabpage.py        | 124 ++++++++++++++++++++++++++++++
 pysollib/tk/wizarddialog.py   | 108 ++++++++++++++++++++++++++
 pysollib/wizardutil.py        |  58 ++++++++------
 9 files changed, 511 insertions(+), 96 deletions(-)
 create mode 100644 pysollib/tk/tabpage.py
 create mode 100644 pysollib/tk/wizarddialog.py

diff --git a/pysollib/customgame.py b/pysollib/customgame.py
index caa38e22..059812b4 100644
--- a/pysollib/customgame.py
+++ b/pysollib/customgame.py
@@ -29,6 +29,7 @@ from hint import AbstractHint, DefaultHint, CautiousDefaultHint
 
 from wizardutil import WizardWidgets
 
+
 # /***********************************************************************
 # //
 # ************************************************************************/
@@ -48,59 +49,118 @@ class CustomGame(Game):
                 v = ss[w.var_name]
             s[w.var_name] = v
         ##from pprint import pprint; pprint(s)
-        foundation = StackWrapper(
-            s['found_type'],
-            base_rank=s['found_base_card'],
-            dir=s['found_dir'],
-            max_move=s['found_max_move'],
-            )
-        max_rounds = s['redeals']
-        if max_rounds >= 0:
-            max_rounds += 1
-        talon = StackWrapper(
-            s['talon'],
-            max_rounds=max_rounds,
-            )
-        row = StackWrapper(
-            s['rows_type'],
-            base_rank=s['rows_base_card'],
-            dir=s['rows_dir'],
-            max_move=s['rows_max_move'],
-            )
-        kw = {'rows'     : s['rows_num'],
+
+        # foundations
+        kw = {
+            'dir': s['found_dir'],
+            'base_rank': s['found_base_card'],
+            }
+        if s['found_type'] not in (Spider_SS_Foundation,
+                                   Spider_AC_Foundation,):
+            kw['max_move'] = s['found_max_move']
+        else:
+            kw['dir'] = -kw['dir']
+            if s['found_base_card'] == KING:
+                kw['base_rank'] = ACE
+            elif s['found_base_card'] == ACE:
+                kw['base_rank'] = KING
+        if s['found_wrap']:
+            kw['mod'] = 13
+        foundation = StackWrapper(s['found_type'], **kw)
+
+        # talon
+        kw = {
+            'max_rounds': s['redeals'],
+            }
+        if s['redeals'] >= 0:
+            kw['max_rounds'] += 1
+        talon = StackWrapper(s['talon'], **kw)
+
+        # rows
+        kw = {
+            'base_rank': s['rows_base_card'],
+            'dir':       s['rows_dir'],
+            'max_move':  s['rows_max_move'],
+            }
+        if s['rows_wrap']:
+            kw['mod'] = 13
+        row = StackWrapper(s['rows_type'], **kw)
+
+        # layout
+        layout_kw = {'rows'     : s['rows_num'],
               'waste'    : False,
               'texts'    : True,
               }
         if s['talon'] is InitialDealTalonStack:
-            kw['texts'] = False
-        if s['talon'] is WasteTalonStack:
-            kw['waste'] = True
-            kw['waste_class'] = WasteStack
-        if int(s['reserves_num']):
-            kw['reserves'] = s['reserves_num']
-            kw['reserve_class'] = s['reserves_type']
+            layout_kw['texts'] = False
+        layout_kw['playcards'] = 12+s['deal_to_rows']
 
-        kw['playcards'] = 12+s['deal_to_rows']
+        # reserves
+        if s['reserves_num']:
+            layout_kw['reserves'] = s['reserves_num']
+            kw = {
+                'max_accept': s['reserves_max_accept'],
+                }
+            if s['reserves_max_accept']:
+                layout_kw['reserve_class'] = StackWrapper(ReserveStack, **kw)
+            else:
+                layout_kw['reserve_class'] = StackWrapper(OpenStack, **kw)
+
+        # waste
+        if s['talon'] is WasteTalonStack:
+            layout_kw['waste'] = True
+            layout_kw['waste_class'] = WasteStack
 
         Layout(self).createGame(layout_method    = s['layout'],
                                 talon_class      = talon,
                                 foundation_class = foundation,
                                 row_class        = row,
-                                **kw
+                                **layout_kw
                                 )
 
+        # shallHighlightMatch
         for c, f in (
-            ((AC_RowStack, UD_AC_RowStack),
-             self._shallHighlightMatch_AC),
-            ((SS_RowStack, UD_SS_RowStack),
-             self._shallHighlightMatch_SS),
-            ((RK_RowStack, UD_RK_RowStack),
-             self._shallHighlightMatch_RK),
-            ):
+                ((Spider_AC_RowStack, Spider_SS_RowStack),
+                 (self._shallHighlightMatch_RK,
+                  self._shallHighlightMatch_RKW)),
+                ((AC_RowStack, UD_AC_RowStack),
+                 (self._shallHighlightMatch_AC,
+                  self._shallHighlightMatch_ACW)),
+                ((SS_RowStack, UD_SS_RowStack),
+                 (self._shallHighlightMatch_SS,
+                  self._shallHighlightMatch_SSW)),
+                ((RK_RowStack, UD_RK_RowStack),
+                 (self._shallHighlightMatch_RK,
+                  self._shallHighlightMatch_RKW)),
+                ((SC_RowStack, UD_SC_RowStack),
+                 (self._shallHighlightMatch_SC,
+                  self._shallHighlightMatch_SCW)),
+                ((BO_RowStack,),
+                 (self._shallHighlightMatch_BO,
+                  self._shallHighlightMatch_BOW)),
+                ):
             if s['rows_type'] in c:
-                self.shallHighlightMatch = f
+                if s['rows_wrap']:
+                    self.shallHighlightMatch = f[1]
+                else:
+                    self.shallHighlightMatch = f[0]
                 break
 
+        # getQuickPlayScore
+        if s['rows_type'] in (Spider_AC_RowStack, Spider_SS_RowStack):
+            self.getQuickPlayScore = self._getSpiderQuickPlayScore
+
+        # canDropCards
+        if s['found_type'] in (Spider_SS_Foundation,
+                               Spider_AC_Foundation,):
+            for stack in self.s.rows:
+                stack.canDropCards = stack.spiderCanDropCards
+
+        if s['found_base_card'] == ANY_RANK:
+            for stack in self.s.foundations:
+                stack.acceptsCards = stack.varyAcceptsCards
+                stack.getBaseCard = stack.getVaryBaseCard
+
 
     def startGame(self):
         frames = 0
@@ -115,7 +175,7 @@ class CustomGame(Game):
                 self.startDealSample()
 
         # deal to rows
-        flip = self.SETTINGS['deal_faceup'] == 'All cards'
+        flip = (self.SETTINGS['deal_faceup'] == 'All cards')
         max_rows = self.SETTINGS['deal_to_rows']
         if self.SETTINGS['deal_type'] == 'Triangle':
             # triangle
@@ -140,12 +200,16 @@ class CustomGame(Game):
         if frames == 0:
             self.startDealSample()
         self.s.talon.dealRowAvail()
+        if isinstance(self.s.talon, InitialDealTalonStack):
+            while self.s.talon.cards:
+                self.s.talon.dealRowAvail()
 
         # deal to waste
         if self.s.waste:
             self.s.talon.dealCards()
 
 
+
 def registerCustomGame(gameclass):
 
     s = gameclass.SETTINGS
diff --git a/pysollib/game.py b/pysollib/game.py
index ad0aaec1..6eff160d 100644
--- a/pysollib/game.py
+++ b/pysollib/game.py
@@ -2038,6 +2038,27 @@ for %d moves.
         return ((card1.rank + 1) % 13 == card2.rank
                 or (card2.rank + 1) % 13 == card1.rank)
 
+    def _shallHighlightMatch_BO(self, stack1, card1, stack2, card2):
+        # by any suit but own
+        return card1.suit != card2.suit and abs(card1.rank-card2.rank) == 1
+
+    def _shallHighlightMatch_BOW(self, stack1, card1, stack2, card2):
+        # by any suit but own with wrapping (only for french games)
+        return (card1.suit != card2.suit
+                and ((card1.rank + 1) % 13 == card2.rank
+                     or (card2.rank + 1) % 13 == card1.rank))
+
+    def _shallHighlightMatch_SC(self, stack1, card1, stack2, card2):
+        # by same color
+        return card1.color == card2.color and abs(card1.rank-card2.rank) == 1
+
+    def _shallHighlightMatch_SCW(self, stack1, card1, stack2, card2):
+        # by same color with wrapping (only for french games)
+        return (card1.color == card2.color
+                and ((card1.rank + 1) % 13 == card2.rank
+                     or (card2.rank + 1) % 13 == card1.rank))
+
+
     #
     # quick-play
     #
diff --git a/pysollib/games/spider.py b/pysollib/games/spider.py
index 557ec15e..876e3c2f 100644
--- a/pysollib/games/spider.py
+++ b/pysollib/games/spider.py
@@ -81,36 +81,8 @@ class Spider_Hint(SpiderType_Hint):
 # //
 # ************************************************************************/
 
-class Spider_SS_Foundation(AbstractFoundationStack):
-    def __init__(self, x, y, game, suit=ANY_SUIT, **cap):
-        kwdefault(cap, dir=-1, base_rank=KING,
-                  min_accept=13, max_accept=13, max_move=0)
-        AbstractFoundationStack.__init__(self, x, y, game, suit, **cap)
-
-    def acceptsCards(self, from_stack, cards):
-        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
-            return 0
-        # now check the cards
-        return isSameSuitSequence(cards, self.cap.mod, self.cap.dir)
-
-
-class Spider_AC_Foundation(Spider_SS_Foundation):
-    def acceptsCards(self, from_stack, cards):
-        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
-            return 0
-        # now check the cards
-        return isAlternateColorSequence(cards, self.cap.mod, self.cap.dir)
-
-
 class Spider_RowStack(Spider_SS_RowStack):
-    def canDropCards(self, stacks):
-        if len(self.cards) < 13:
-            return (None, 0)
-        cards = self.cards[-13:]
-        for s in stacks:
-            if s is not self and s.acceptsCards(self, cards):
-                return (s, 13)
-        return (None, 0)
+    canDropCards = BasicRowStack.spiderCanDropCards
 
 
 class SuperMoveSpider_RowStack(SuperMoveStack_StackMethods, Spider_RowStack):
diff --git a/pysollib/stack.py b/pysollib/stack.py
index ade153f5..caae7224 100644
--- a/pysollib/stack.py
+++ b/pysollib/stack.py
@@ -58,6 +58,8 @@ __all__ = ['cardsFaceUp',
            'RK_FoundationStack',
            'AC_FoundationStack',
            'SC_FoundationStack',
+           'Spider_SS_Foundation',
+           'Spider_AC_Foundation',
            #'SequenceStack_StackMethods',
            'BasicRowStack',
            'SequenceRowStack',
@@ -1492,11 +1494,14 @@ class Stack:
     def getBaseCard(self):
         return ''
 
-    def _getBaseCard(self):
+    def _getBaseCard(self, rank=None):
         # FIXME: no-french games
         if self.cap.max_accept == 0:
             return ''
-        br = self.cap.base_rank
+        if rank is None:
+            br = self.cap.base_rank
+        else:
+            br = rank
         s = _('Base card - %s.')
         if br == NO_RANK: s = _('Empty row cannot be filled.')
         elif br == -1: s = s % _('any card')
@@ -2068,6 +2073,28 @@ class AbstractFoundationStack(OpenStack):
     def getHelp(self):
         return _('Foundation.')
 
+    def varyAcceptsCards(self, from_stack, cards):
+        # if base rank of foundations is vary
+        subclass = self.__class__    # derived class (SS_FoundationStack, etc)
+        assert subclass is not AbstractFoundationStack
+        if self.cards:
+            return subclass.acceptsCards(self, from_stack, cards)
+        if not subclass.acceptsCards(self, from_stack, cards):
+            return False
+        # this stack don't have cards: check base rank of other stacks
+        for s in self.game.s.foundations:
+            if s.cards:
+                base_card = s.cards[0]
+                return base_card.rank == cards[0].rank
+        return True                     # all foundations is empty
+
+    def getVaryBaseCard(self):
+        rank = None
+        for s in self.game.s.foundations:
+            if s.cards:
+                rank = s.cards[0].rank
+        return self._getBaseCard(rank=rank)
+
 
 # A SameSuit_FoundationStack is the typical Foundation stack.
 # It builds up in rank and suit.
@@ -2146,6 +2173,29 @@ class SC_FoundationStack(SS_FoundationStack):
         else:                  return _('Foundation. Build by same rank.')
 
 
+# Spider-type foundations
+class Spider_SS_Foundation(AbstractFoundationStack):
+    def __init__(self, x, y, game, suit=ANY_SUIT, **cap):
+        kwdefault(cap, dir=-1, base_rank=KING,
+                  min_accept=13, max_accept=13, max_move=0)
+        AbstractFoundationStack.__init__(self, x, y, game, suit, **cap)
+
+    def acceptsCards(self, from_stack, cards):
+        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
+            return 0
+        # now check the cards
+        return isSameSuitSequence(cards, self.cap.mod, self.cap.dir)
+
+
+class Spider_AC_Foundation(Spider_SS_Foundation):
+    def acceptsCards(self, from_stack, cards):
+        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
+            return 0
+        # now check the cards
+        return isAlternateColorSequence(cards, self.cap.mod, self.cap.dir)
+
+
+
 # /***********************************************************************
 # // Abstract classes for row stacks.
 # ************************************************************************/
@@ -2192,6 +2242,16 @@ class BasicRowStack(OpenStack):
     #def getBaseCard(self):
     #    return self._getBaseCard()
 
+    def spiderCanDropCards(self, stacks):
+        # drop whole sequence
+        if len(self.cards) < 13:
+            return (None, 0)
+        cards = self.cards[-13:]
+        for s in stacks:
+            if s is not self and s.acceptsCards(self, cards):
+                return (s, 13)
+        return (None, 0)
+
 
 # Abstract class.
 class SequenceRowStack(SequenceStack_StackMethods, BasicRowStack):
diff --git a/pysollib/tile/wizarddialog.py b/pysollib/tile/wizarddialog.py
index efae86f1..02fc377e 100644
--- a/pysollib/tile/wizarddialog.py
+++ b/pysollib/tile/wizarddialog.py
@@ -55,7 +55,8 @@ class WizardDialog(MfxDialog):
         for w in WizardWidgets:
             if isinstance(w, basestring):
                 frame = Frame(notebook)
-                notebook.add(frame, text=w)
+                notebook.add(frame, text=w, sticky='nsew', padding=5)
+                frame.columnconfigure(1, weight=1)
                 row = 0
                 continue
 
@@ -64,12 +65,11 @@ class WizardDialog(MfxDialog):
             if w.widget == 'entry':
                 w.variable = var = StringVar()
                 en = Entry(frame, textvariable=var)
-                en.grid(row=row, column=1, sticky='ew')
+                en.grid(row=row, column=1, sticky='ew', padx=2, pady=2)
             elif w.widget == 'menu':
                 w.variable = var = StringVar()
-                ##OptionMenu(frame, var, *w.values).grid(row=row, column=1)
                 cb = Combobox(frame, values=tuple(w.values), textvariable=var,
-                              state='readonly', width=20)
+                              state='readonly', width=32)
                 cb.grid(row=row, column=1, sticky='ew', padx=2, pady=2)
             elif w.widget == 'spin':
                 w.variable = var = IntVar()
@@ -78,7 +78,11 @@ class WizardDialog(MfxDialog):
                 s = PysolScale(frame, from_=from_, to=to, resolution=1,
                                orient='horizontal',
                                variable=var)
-                s.grid(row=row, column=1, sticky='ew')
+                s.grid(row=row, column=1, sticky='ew', padx=2, pady=2)
+            elif w.widget == 'check':
+                w.variable = var = BooleanVar()
+                ch = Checkbutton(frame, variable=var, takefocus=False)
+                ch.grid(row=row, column=1, sticky='ew', padx=2, pady=2)
 
             if w.current_value is None:
                 var.set(w.default)
diff --git a/pysollib/tk/menubar.py b/pysollib/tk/menubar.py
index fbd44c38..f24a29b7 100644
--- a/pysollib/tk/menubar.py
+++ b/pysollib/tk/menubar.py
@@ -61,6 +61,7 @@ from selecttile import SelectTileDialogWithPreview
 from findcarddialog import connect_game_find_card_dialog, destroy_find_card_dialog
 from solverdialog import connect_game_solver_dialog
 from tkwrap import MfxRadioMenuItem, MfxCheckMenuItem, StringVar
+from wizarddialog import WizardDialog
 
 #from toolbar import TOOLBAR_BUTTONS
 from tkconst import TOOLBAR_BUTTONS
@@ -401,6 +402,10 @@ class PysolMenubar(PysolMenubarActions):
 
         menu.add_command(label=n_("Restart"), command=self.mRestart, accelerator=m+"G")
 
+        menu.add_separator()
+        menu.add_command(label=n_("Solitaire &Wizard"), command=self.mWizard)
+        menu.add_command(label=n_("Edit current game"), command=self.mWizardEdit)
+
         menu = MfxMenu(self.__menubar, label=n_("&Game"))
         menu.add_command(label=n_("&Deal cards"), command=self.mDeal, accelerator="D")
         menu.add_command(label=n_("&Auto drop"), command=self.mDrop, accelerator="A")
@@ -1340,3 +1345,46 @@ class PysolMenubar(PysolMenubarActions):
         else:
             if self._cancelDrag(break_pause=True): return
             self.game.showStackDesc()
+
+
+    def wizardDialog(self, edit=False):
+        from pysollib.wizardutil import write_game, reset_wizard
+        if edit:
+            reset_wizard(self.game)
+        d = WizardDialog(self.top, _('Solitaire Wizard'), self.app)
+        if d.status == 0 and d.button == 0:
+            try:
+                if edit:
+                    gameid = write_game(self.app, game=self.game)
+                else:
+                    gameid = write_game(self.app)
+            except Exception, err:
+                if DEBUG:
+                    traceback.print_exc()
+                d = MfxMessageDialog(self.top, title=_('Save game error'),
+                                     text=_('''
+Error while saving game.
+
+%s
+''') % str(err),
+                                     bitmap='error')
+                return
+            if SELECT_GAME_MENU and not edit:
+                gi = self.app.getGameInfo(gameid)
+                label = gettext(gi.name)
+                menu = self.__menupath[".menubar.select.frenchgames.cusomgames"][2]
+                menu.add_radiobutton(command=self.mSelectGame,
+                                     variable=self.tkopt.gameid,
+                                     value=gameid, label=label, name=None)
+            self.tkopt.gameid.set(gameid)
+            self._mSelectGame(gameid, force=True)
+
+
+    def mWizard(self, *event):
+        if self._cancelDrag(break_pause=False): return
+        self.wizardDialog()
+
+    def mWizardEdit(self, *event):
+        if self._cancelDrag(break_pause=False): return
+        self.wizardDialog(edit=True)
+
diff --git a/pysollib/tk/tabpage.py b/pysollib/tk/tabpage.py
new file mode 100644
index 00000000..028bba84
--- /dev/null
+++ b/pysollib/tk/tabpage.py
@@ -0,0 +1,124 @@
+#
+# This file is part of IDLE project - http://www.python.org/idle/
+#
+"""
+a couple of classes for implementing partial tabbed-page like behaviour
+"""
+
+from Tkinter import *
+
+MYRIDGE, MYRAISED = RAISED, RIDGE
+#MYRIDGE, MYRAISED = RIDGE, RAISED
+
+class InvalidTabPage(Exception): pass
+class AlreadyExists(Exception): pass
+
+class PageTab(Frame):
+    """
+    a 'page tab' like framed button
+    """
+    def __init__(self,parent):
+        Frame.__init__(self, parent, borderwidth=2, relief=MYRIDGE)
+        self.button=Radiobutton(self, padx=5, pady=5, takefocus=FALSE,
+                                indicatoron=FALSE, highlightthickness=0,
+                                borderwidth=0, selectcolor=self.cget('bg'))
+        self.button.pack()
+
+class TabPageSet(Frame):
+    """
+    a set of 'pages' with TabButtons for controlling their display
+    """
+    def __init__(self,parent,pageNames=[],**kw):
+        """
+        pageNames - a list of strings, each string will be the dictionary key
+        to a page's data, and the name displayed on the page's tab. Should be
+        specified in desired page order. The first page will be the default
+        and first active page.
+        """
+        Frame.__init__(self, parent, kw)
+        self.grid_location(0,0)
+        self.columnconfigure(0,weight=1)
+        self.rowconfigure(1,weight=1)
+        self.tabBar=Frame(self)
+        self.tabBar.grid(row=0,column=0,sticky=EW)
+        self.activePage=StringVar(self)
+        self.defaultPage=''
+        self.pages={}
+        for name in pageNames:
+            self.AddPage(name)
+
+    def ChangePage(self,pageName=None):
+        if pageName:
+            if pageName in self.pages.keys():
+                self.activePage.set(pageName)
+            else:
+                raise InvalidTabPage, 'Invalid TabPage Name'
+        ## pop up the active 'tab' only
+        for page in self.pages.keys():
+            self.pages[page]['tab'].config(relief=MYRIDGE)
+        self.pages[self.GetActivePage()]['tab'].config(relief=MYRAISED)
+        ## switch page
+        self.pages[self.GetActivePage()]['page'].lift()
+
+    def GetActivePage(self):
+        return self.activePage.get()
+
+    def AddPage(self,pageName):
+        if pageName in self.pages.keys():
+            raise AlreadyExists, 'TabPage Name Already Exists'
+        self.pages[pageName]={
+            'tab': PageTab(self.tabBar),
+            'page': Frame(self,borderwidth=2,relief=RAISED)
+            }
+        self.pages[pageName]['tab'].button.config(
+            text=pageName,
+            command=self.ChangePage,
+            variable=self.activePage,
+            value=pageName
+            )
+        self.pages[pageName]['tab'].pack(side=LEFT)
+        self.pages[pageName]['page'].grid(row=1,column=0,sticky=NSEW)
+        if len(self.pages)==1: # adding first page
+            self.defaultPage=pageName
+            self.activePage.set(self.defaultPage)
+            self.ChangePage()
+
+    def RemovePage(self,pageName):
+        if not pageName in self.pages.keys():
+            raise InvalidTabPage, 'Invalid TabPage Name'
+        self.pages[pageName]['tab'].pack_forget()
+        self.pages[pageName]['page'].grid_forget()
+        self.pages[pageName]['tab'].destroy()
+        self.pages[pageName]['page'].destroy()
+        del(self.pages[pageName])
+        # handle removing last remaining, or default, or active page
+        if not self.pages: # removed last remaining page
+            self.defaultPage=''
+            return
+        if pageName==self.defaultPage: # set a new default page
+            self.defaultPage=\
+                self.tabBar.winfo_children()[0].button.cget('text')
+        if pageName==self.GetActivePage(): # set a new active page
+            self.activePage.set(self.defaultPage)
+        self.ChangePage()
+
+if __name__ == '__main__':
+    #test dialog
+    root=Tk()
+    tabPage=TabPageSet(root,pageNames=['Foobar','Baz'])
+    tabPage.pack(expand=TRUE,fill=BOTH)
+    Label(tabPage.pages['Foobar']['page'],text='Foo',pady=20).pack()
+    Label(tabPage.pages['Foobar']['page'],text='Bar',pady=20).pack()
+    Label(tabPage.pages['Baz']['page'],text='Baz').pack()
+    entryPgName=Entry(root)
+    buttonAdd=Button(root,text='Add Page',
+            command=lambda:tabPage.AddPage(entryPgName.get()))
+    buttonRemove=Button(root,text='Remove Page',
+            command=lambda:tabPage.RemovePage(entryPgName.get()))
+    labelPgName=Label(root,text='name of page to add/remove:')
+    buttonAdd.pack(padx=5,pady=5)
+    buttonRemove.pack(padx=5,pady=5)
+    labelPgName.pack(padx=5)
+    entryPgName.pack(padx=5)
+    tabPage.ChangePage()
+    root.mainloop()
diff --git a/pysollib/tk/wizarddialog.py b/pysollib/tk/wizarddialog.py
new file mode 100644
index 00000000..78c21f72
--- /dev/null
+++ b/pysollib/tk/wizarddialog.py
@@ -0,0 +1,108 @@
+##---------------------------------------------------------------------------##
+##
+## PySol -- a Python Solitaire game
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; see the file COPYING.
+## If not, write to the Free Software Foundation, Inc.,
+## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+##
+##---------------------------------------------------------------------------##
+
+__all__ = ['WizardDialog']
+
+
+# imports
+from Tkinter import *
+from tabpage import TabPageSet
+
+# PySol imports
+from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct
+from pysollib.wizardutil import WizardWidgets
+
+# Toolkit imports
+from tkwidget import MfxDialog
+
+
+# /***********************************************************************
+# //
+# ************************************************************************/
+
+class WizardDialog(MfxDialog):
+    def __init__(self, parent, title, app, **kw):
+        kw = self.initKw(kw)
+        MfxDialog.__init__(self, parent, title, kw.resizable, kw.default)
+        top_frame, bottom_frame = self.createFrames(kw)
+        self.createBitmaps(top_frame, kw)
+
+        frame = Frame(top_frame)
+        frame.pack(expand=True, fill='both', padx=10, pady=10)
+        frame.columnconfigure(0, weight=1)
+
+        notebook = TabPageSet(frame)
+        notebook.pack(expand=True, fill='both')
+
+        for w in WizardWidgets:
+            if isinstance(w, basestring):
+                p = notebook.AddPage(w)
+                frame = Frame(notebook.pages[w]['page'])
+                frame.pack(expand=True, fill='both', padx=2, pady=4)
+                frame.columnconfigure(1, weight=1)
+                row = 0
+                continue
+
+            Label(frame, text=w.label).grid(row=row, column=0, padx=2)
+
+            if w.widget == 'entry':
+                w.variable = var = StringVar()
+                en = Entry(frame, textvariable=var)
+                en.grid(row=row, column=1, sticky='ew', padx=2)
+            elif w.widget == 'menu':
+                w.variable = var = StringVar()
+                om = OptionMenu(frame, var, *w.values)
+                om.grid(row=row, column=1, sticky='ew', padx=2)
+            elif w.widget == 'spin':
+                w.variable = var = IntVar()
+                from_, to = w.values
+                s = Scale(frame, from_=from_, to=to, resolution=1,
+                               orient='horizontal',
+                               variable=var)
+                s.grid(row=row, column=1, sticky='ew', padx=2)
+            elif w.widget == 'check':
+                w.variable = var = BooleanVar()
+                ch = Checkbutton(frame, variable=var,
+                                 takefocus=False, anchor='w')
+                ch.grid(row=row, column=1, sticky='ew', padx=2, pady=2)
+
+            if w.current_value is None:
+                var.set(w.default)
+            else:
+                var.set(w.current_value)
+
+            row += 1
+
+        notebook.ChangePage()
+
+        focus = self.createButtons(bottom_frame, kw)
+        self.mainloop(focus, kw.timeout)
+
+
+    def initKw(self, kw):
+        kw = KwStruct(kw,
+                      strings=(_('&OK'), _('&Cancel')),
+                      default=0,
+                      )
+        return MfxDialog.initKw(self, kw)
+
+
+
diff --git a/pysollib/wizardutil.py b/pysollib/wizardutil.py
index ca086af4..4e6e5200 100644
--- a/pysollib/wizardutil.py
+++ b/pysollib/wizardutil.py
@@ -117,17 +117,22 @@ Redeals = WizSetting(
     var_name = 'redeals',
     )
 FoundType = WizSetting(
-    values_map = ((n_('Same suit'),       SS_FoundationStack),
-                  (n_('Alternate color'), AC_FoundationStack),
-                  (n_('Same color'),      SC_FoundationStack),
-                  (n_('Rank'),            RK_FoundationStack),
+    values_map = ((n_('Same suit'),              SS_FoundationStack),
+                  (n_('Alternate color'),        AC_FoundationStack),
+                  (n_('Same color'),             SC_FoundationStack),
+                  (n_('Rank'),                   RK_FoundationStack),
+                  (n_('Spider same suit'),       Spider_SS_Foundation),
+                  (n_('Spider alternate color'), Spider_AC_Foundation),
                   ),
     default = n_('Same suit'),
     label = _('Type:'),
     var_name = 'found_type',
     )
 FoundBaseCard = WizSetting(
-    values_map = ((n_('Ace'), ACE), (n_('King'), KING)),
+    values_map = ((n_('Ace'),  ACE),
+                  (n_('King'), KING),
+                  (n_('Any'),  ANY_RANK),
+                  ),
     default = n_('Ace'),
     label = _('Base card:'),
     var_name = 'found_base_card',
@@ -139,10 +144,11 @@ FoundDir = WizSetting(
     var_name = 'found_dir',
     )
 FoundWrap = WizSetting(
-    values_map = ((n_('Yes'), True), (n_('No'), False)),
-    default = n_('No'),
+    values_map = (True, False),
+    default = False,
     label = _('Wrapping:'),
     var_name = 'found_wrap',
+    widget = 'check',
     )
 FoundMaxMove = WizSetting(
     values_map = ((n_('No move'), 0,), (n_('One card'), 1)),
@@ -158,11 +164,17 @@ RowsNum = WizSetting(
     var_name = 'rows_num',
     )
 RowsType = WizSetting(
-    values_map = ((n_('Same suit'),             SS_RowStack),
-                  (n_('Alternate color'),       AC_RowStack),
-                  (n_('Same color'),            SC_RowStack),
-                  (n_('Rank'),                  RK_RowStack),
-                  (n_('Any suit but the same'), BO_RowStack),
+    values_map = ((n_('Same suit'),                     SS_RowStack),
+                  (n_('Alternate color'),               AC_RowStack),
+                  (n_('Same color'),                    SC_RowStack),
+                  (n_('Rank'),                          RK_RowStack),
+                  (n_('Any suit but the same'),         BO_RowStack),
+                  (n_('Up or down by same suit'),       UD_SS_RowStack),
+                  (n_('Up or down by alternate color'), UD_AC_RowStack),
+                  (n_('Up or down by rank'),            UD_RK_RowStack),
+                  (n_('Up or down by same color'),      UD_SC_RowStack),
+                  (n_('Spider same suit'),              Spider_SS_RowStack),
+                  (n_('Spider alternate color'),        Spider_AC_RowStack),
                   ),
     default = n_('Alternate color'),
     label = _('Type:'),
@@ -185,10 +197,11 @@ RowsDir = WizSetting(
     var_name = 'rows_dir',
     )
 RowsWrap = WizSetting(
-    values_map = ((n_('Yes'), True), (n_('No'), False)),
-    default = n_('No'),
+    values_map = (True, False),
+    default = False,
     label = _('Wrapping:'),
     var_name = 'rows_wrap',
+    widget = 'check',
     )
 RowsMaxMove = WizSetting(
     values_map = ((n_('One card'), 1), (n_('Unlimited'), UNLIMITED_MOVES)),
@@ -203,13 +216,12 @@ ReservesNum = WizSetting(
     label = _('Number of reserves:'),
     var_name = 'reserves_num',
     )
-ReservesType = WizSetting(
-    values_map = ((n_('FreeCell'), ReserveStack),
-                  (n_('Reserve'),  OpenStack),
-                  ),
-    default = n_('FreeCell'),
-    label = n_('Type of reserves:'),
-    var_name = 'reserves_type',
+ReservesMaxAccept = WizSetting(
+    values_map = (0, 20),
+    default = 1,
+    widget = 'spin',
+    label = _('Max accept:'),
+    var_name = 'reserves_max_accept',
     )
 DealType = WizSetting(
     values_map = ((n_('Triangle'),  'triangle'),
@@ -264,7 +276,7 @@ WizardWidgets = (
     RowsMaxMove,
     _('Reserves'),
     ReservesNum,
-    ReservesType,
+    ReservesMaxAccept,
     _('Initial dealing'),
     DealType,
     DealFaceUp,
@@ -320,6 +332,8 @@ class MyCustomGame(CustomGame):
         if isinstance(v, int):
             fd.write("        '%s': %i,\n" % (w.var_name, v))
         else:
+            if w.var_name == 'name' and not v:
+                v = 'Invalid Game Name'
             fd.write("        '%s': '%s',\n" % (w.var_name, v))
     fd.write("        'gameid': %i,\n" % gameid)
     fd.write("        'file': '%s',\n" % os.path.split(fn)[1])