diff --git a/README b/README index 2148ed9d..d036ca89 100644 --- a/README +++ b/README @@ -28,6 +28,14 @@ or just run from the source directory: $ python pysol.py +** Freecell Solver ** +If you want to use Solver, you should configure freecell-solver with following +options: +--enable-max-num-freecells=8 +--enable-max-num-stacks=20 +--enable-max-num-initial-cards-per-stack=60 +(or edit config.h) + Install Extras. --------------- diff --git a/pysollib/actions.py b/pysollib/actions.py index 9fe9e751..0606aab1 100644 --- a/pysollib/actions.py +++ b/pysollib/actions.py @@ -44,6 +44,7 @@ from pysolrandom import constructRandom from settings import PACKAGE, PACKAGE_URL from settings import TOP_TITLE from settings import DEBUG +from gamedb import GI # stats imports from stats import FileStatsFormatter @@ -95,6 +96,7 @@ class PysolMenubarActions: find_card = 0, rules = 0, pause = 0, + custom_game = 0, ) def connectGame(self, game): @@ -192,6 +194,8 @@ class PysolMenubarActions: ms.rules = 1 if not game.finished: ms.pause = 1 + if game.gameinfo.si.game_type == GI.GT_CUSTOM: + ms.custom_game = 1 # update menu items and toolbar def _updateMenus(self): @@ -208,6 +212,7 @@ class PysolMenubarActions: self.setMenuState(ms.redo, "edit.redoall") self.updateBookmarkMenuState() self.setMenuState(ms.restart, "edit.restart") + self.setMenuState(ms.custom_game, "edit.editcurrentgame") # Game menu self.setMenuState(ms.deal, "game.dealcards") self.setMenuState(ms.autodrop, "game.autodrop") @@ -264,9 +269,9 @@ class PysolMenubarActions: self.game.endGame() self.game.quitGame(self.game.id) - def _mSelectGame(self, id, random=None): + def _mSelectGame(self, id, random=None, force=False): if self._cancelDrag(): return - if self.game.id == id: + if not force and self.game.id == id: return if self.changed(): if not self.game.areYouSure(_("Select game")): diff --git a/pysollib/app.py b/pysollib/app.py index 7db65e9a..b98aea16 100644 --- a/pysollib/app.py +++ b/pysollib/app.py @@ -1420,12 +1420,12 @@ Please select a %s type %s. m = re.search(r"^(.+)\.py$", name) n = os.path.join(dir, name) if m and os.path.isfile(n): - p = sys.path[:] try: loadGame(m.group(1), n) except Exception, ex: + if DEBUG: + traceback.print_exc() print_err(_("error loading plugin %s: %s") % (n, ex)) - sys.path = p # diff --git a/pysollib/customgame.py b/pysollib/customgame.py new file mode 100644 index 00000000..caa38e22 --- /dev/null +++ b/pysollib/customgame.py @@ -0,0 +1,170 @@ +##---------------------------------------------------------------------------## +## +## 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. +## +##---------------------------------------------------------------------------## + +from gamedb import registerGame, GameInfo, GI +from util import * +from stack import * +from game import Game +from layout import Layout +from hint import AbstractHint, DefaultHint, CautiousDefaultHint +#from pysoltk import MfxCanvasText + +from wizardutil import WizardWidgets + +# /*********************************************************************** +# // +# ************************************************************************/ + +class CustomGame(Game): + + def createGame(self): + + ss = self.SETTINGS + s = {} + for w in WizardWidgets: + if isinstance(w, basestring): + continue + if w.widget == 'menu': + v = dict(w.values_map)[ss[w.var_name]] + else: + 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'], + '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'] + + kw['playcards'] = 12+s['deal_to_rows'] + + Layout(self).createGame(layout_method = s['layout'], + talon_class = talon, + foundation_class = foundation, + row_class = row, + **kw + ) + + 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), + ): + if s['rows_type'] in c: + self.shallHighlightMatch = f + break + + + def startGame(self): + frames = 0 + + # deal to reserves + n = self.SETTINGS['deal_to_reserves'] + for i in range(n): + self.s.talon.dealRowAvail(rows=self.s.reserves, + flip=True, frames=frames) + if frames == 0 and len(self.s.talon.cards) < 16: + frames = -1 + self.startDealSample() + + # deal to rows + flip = self.SETTINGS['deal_faceup'] == 'All cards' + max_rows = self.SETTINGS['deal_to_rows'] + if self.SETTINGS['deal_type'] == 'Triangle': + # triangle + for i in range(1, len(self.s.rows)): + self.s.talon.dealRowAvail(rows=self.s.rows[i:], + flip=flip, frames=frames) + max_rows -= 1 + if max_rows == 1: + break + if frames == 0 and len(self.s.talon.cards) < 16: + frames = -1 + self.startDealSample() + + else: + # rectangle + for i in range(max_rows-1): + self.s.talon.dealRowAvail(rows=self.s.rows, + flip=flip, frames=frames) + if frames == 0 and len(self.s.talon.cards) < 16: + frames = -1 + self.startDealSample() + if frames == 0: + self.startDealSample() + self.s.talon.dealRowAvail() + + # deal to waste + if self.s.waste: + self.s.talon.dealCards() + + +def registerCustomGame(gameclass): + + s = gameclass.SETTINGS + + for w in WizardWidgets: + if isinstance(w, basestring): + continue + if w.var_name == 'decks': + v = s['decks'] + decks = dict(w.values_map)[v] + if w.var_name == 'redeals': + v = s['redeals'] + redeals = dict(w.values_map)[v] + if w.var_name == 'skill_level': + v = s['skill_level'] + skill_level = dict(w.values_map)[v] + gameid = s['gameid'] + + registerGame(GameInfo(gameid, gameclass, s['name'], + GI.GT_CUSTOM | GI.GT_ORIGINAL, + decks, redeals, skill_level)) + diff --git a/pysollib/gamedb.py b/pysollib/gamedb.py index 6f346226..93b29ba2 100644 --- a/pysollib/gamedb.py +++ b/pysollib/gamedb.py @@ -98,6 +98,7 @@ class GI: GT_TERRACE = 32 GT_YUKON = 33 GT_SHISEN_SHO = 34 + GT_CUSTOM = 40 # extra flags GT_BETA = 1 << 12 # beta version of game driver GT_CHILDREN = 1 << 13 # *not used* @@ -173,6 +174,7 @@ class GI: (n_("Two-Deck games"),lambda gi, gt=GT_2DECK_TYPE: gi.si.game_type == gt), (n_("Three-Deck games"),lambda gi, gt=GT_3DECK_TYPE: gi.si.game_type == gt), (n_("Four-Deck games"),lambda gi, gt=GT_4DECK_TYPE: gi.si.game_type == gt), + (n_("Cusom games"), lambda gi, gt=GT_CUSTOM: gi.si.game_type == gt), ) SELECT_ORIGINAL_GAME_BY_TYPE = ( @@ -471,7 +473,8 @@ class GameManager: self.__all_games = {} # includes hidden games self.__all_gamenames = {} # includes hidden games self.__games_for_solver = [] - self.loading_plugin = 0 + self.loading_plugin = False + self.check_game = True self.registered_game_types = {} def getSelected(self): @@ -506,12 +509,12 @@ class GameManager: raise GameInfoException("duplicate game altname %s: %s" % (gi.id, n)) - def register(self, gi): + def register(self, gi, check_game=True): ##print gi.id, gi.short_name.encode('utf-8') if not isinstance(gi, GameInfo): raise GameInfoException("wrong GameInfo class") gi.plugin = self.loading_plugin - if self.loading_plugin or CHECK_GAMES: + if self.check_game and (self.loading_plugin or CHECK_GAMES): self._check_game(gi) ##if 0 and gi.si.game_flags & GI.GT_XORIGINAL: ## return @@ -610,9 +613,10 @@ def registerGame(gameinfo): return gameinfo -def loadGame(modname, filename, plugin=1): +def loadGame(modname, filename, plugin=True, check_game=True): ##print "load game", modname, filename GAME_DB.loading_plugin = plugin + GAME_DB.check_game = check_game module = imp.load_source(modname, filename) ##execfile(filename, globals(), globals()) diff --git a/pysollib/hint.py b/pysollib/hint.py index a15a6448..08b55dbb 100644 --- a/pysollib/hint.py +++ b/pysollib/hint.py @@ -39,7 +39,7 @@ import os, sys import time # PySol imports -from settings import USE_FREECELL_SOLVER, FCS_COMMAND +from settings import DEBUG, USE_FREECELL_SOLVER, FCS_COMMAND from mfxutil import destruct from util import KING @@ -702,7 +702,7 @@ class FreeCellSolver_Hint: self.dialog = dialog self.game_type = game_type self.options = { - 'method': 'dfs', + 'method': 'soft-dfs', 'max_iters': 10000, 'max_depth': 1000, 'progress': False, @@ -768,7 +768,10 @@ class FreeCellSolver_Hint: b = '' for s in self.game.s.foundations: if s.cards: - ss = self.card2str2(s.cards[-1]) + if 'preset' in game_type and game_type['preset'] == 'simple_simon': + ss = self.card2str2(s.cards[0]) + else: + ss = self.card2str2(s.cards[-1]) b += ' ' + ss if b: board += 'Founds:' + b + '\n' @@ -792,16 +795,18 @@ class FreeCellSolver_Hint: b += cs + ' ' board = board + b.strip() + '\n' # - ##print '--------------------\n', board, '--------------------' + if DEBUG: + print '--------------------\n', board, '--------------------' # args = [] ##args += ['-sam', '-p', '-opt', '--display-10-as-t'] args += ['-m', '-p', '-opt'] - if self.options['preset'] and self.options['preset'] != 'none': - args += ['-l', self.options['preset']] if progress: args += ['--iter-output'] - ##args += ['-s'] + if DEBUG: + args += ['-s'] + if self.options['preset'] and self.options['preset'] != 'none': + args += ['--load-config', self.options['preset']] args += ['--max-iters', self.options['max_iters'], '--max-depth', self.options['max_depth'], '--method', self.options['method'], @@ -820,7 +825,8 @@ class FreeCellSolver_Hint: args += ['--empty-stacks-filled-by', game_type['esf']] command = FCS_COMMAND+' '+' '.join([str(i) for i in args]) - ##print command + if DEBUG: + print command pin, pout, perr = os.popen3(command) pin.write(board) pin.close() @@ -830,14 +836,16 @@ class FreeCellSolver_Hint: 'stack' : game.s.rows, 'freecell' : game.s.reserves, } - ##start_time = time.time() + if DEBUG: + start_time = time.time() if progress: # iteration output iter = 0 depth = 0 states = 0 for s in pout: - ##print s, + if DEBUG >= 5: + print s, if s.startswith('Iter'): #print `s` iter = int(s[10:-1]) @@ -857,7 +865,8 @@ class FreeCellSolver_Hint: hints = [] for s in pout: - #print s, + if DEBUG: + print s, # TODO: # Total number of states checked is 6. # This scan generated 6 states. @@ -899,7 +908,8 @@ class FreeCellSolver_Hint: ##print src, dest, ncards # - ##print 'time:', time.time()-start_time + if DEBUG: + print 'time:', time.time()-start_time ##print perr.read(), self.hints = hints diff --git a/pysollib/layout.py b/pysollib/layout.py index 311d5e9f..990be50f 100644 --- a/pysollib/layout.py +++ b/pysollib/layout.py @@ -330,10 +330,10 @@ class Layout: # FreeCell layout # - top: free cells, foundations # - below: rows - # - left bottom: talon + # - left bottom: talon, waste # - def freeCellLayout(self, rows, reserves, texts=0, playcards=18): + def freeCellLayout(self, rows, reserves, waste=0, texts=0, playcards=18): S = self.__createStack CW, CH = self.CW, self.CH XM, YM = self.XM, self.YM @@ -371,8 +371,18 @@ class Layout: x, y = XM, h - YS self.s.talon = s = S(x, y) if texts: - # place text right of stack - self._setText(s, anchor="se") + if waste: + # place text top of stack + self._setText(s, anchor="n") + else: + # place text right of stack + self._setText(s, anchor="se") + if waste: + x += XS + self.s.waste = s = S(x, y) + if texts: + # place text top of stack + self._setText(s, anchor="n") # set window self.size = (w, h) @@ -394,6 +404,7 @@ class Layout: decks = self.game.gameinfo.decks suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + w = XM + max(rows+decks, reserves+2+waste)*XS if reserves: h = YS+(playcards-1)*self.YOFFSET+YS else: @@ -409,6 +420,7 @@ class Layout: self.setRegion(self.s.rows, (-999, -999, x - CW / 2, 999999)) # create foundations + x = w - decks*XS for suit in range(suits): for i in range(decks): self.s.foundations.append(S(x+i*XS, y, suit=suit)) @@ -435,16 +447,16 @@ class Layout: x += XS # set window - self.size = (XM + (max(rows, reserves)+decks)*XS, h) + self.size = (w, h) # # Harp layout - # - top: rows + # - top: reserves, rows # - bottom: foundations, waste, talon # - def harpLayout(self, rows, waste, texts=1, playcards=19): + def harpLayout(self, rows, waste, reserves=0, texts=1, playcards=19): S = self.__createStack CW, CH = self.CW, self.CH XM, YM = self.XM, self.YM @@ -453,19 +465,29 @@ class Layout: decks = self.game.gameinfo.decks suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) - w = max(rows*XS, (suits*decks+waste+1)*XS, (suits*decks+1)*XS+2*XM) + w = max(reserves*XS, rows*XS, (suits*decks+waste+1)*XS, + (suits*decks+1)*XS+2*XM) w = XM + w # set size so that at least 19 cards are fully playable h = YS + (playcards-1)*self.YOFFSET h = max(h, 3*YS) if texts: h += self.TEXT_HEIGHT + if reserves: + h += YS # top - x, y = (w - (rows*XS - XM))/2, YM + y = YM + if reserves: + x = (w - (reserves*XS - XM))/2 + for i in range(reserves): + self.s.reserves.append(S(x, y)) + x += XS + y += YS + x = (w - (rows*XS - XM))/2 for i in range(rows): self.s.rows.append(S(x, y)) - x = x + XS + x += XS # bottom x, y = XM, YM + h @@ -493,10 +515,12 @@ class Layout: # # Klondike layout # - top: talon, waste, foundations - # - bottom: rows + # - below: rows + # - bottom: reserves # - def klondikeLayout(self, rows, waste, texts=1, playcards=16, center=1, text_height=0): + def klondikeLayout(self, rows, waste, reserves=0, + texts=1, playcards=16, center=1, text_height=0): S = self.__createStack CW, CH = self.CW, self.CH XM, YM = self.XM, self.YM @@ -507,7 +531,7 @@ class Layout: foundrows = 1 + (suits > 5) frows = decks * suits / foundrows toprows = 1 + waste + frows - maxrows = max(rows, toprows) + maxrows = max(rows, toprows, reserves) # set size so that at least 2/3 of a card is visible with 16 cards h = CH * 2 / 3 + (playcards - 1) * self.YOFFSET @@ -544,7 +568,7 @@ class Layout: x = x + XS y = y + YS - # bottom + # below x = XM if rows < maxrows: x += (maxrows-rows) * XS/2 ##y += YM * (3 - foundrows) @@ -554,6 +578,15 @@ class Layout: self.s.rows.append(S(x, y)) x = x + XS + # bottom + if reserves: + x = (maxrows-reserves)*XS/2 + y = h + YM + YS * foundrows + h += YS + for i in range(reserves): + self.s.reserves.append(S(x, y)) + x += XS + # set window self.size = (XM + maxrows * XS, h + YM + YS * foundrows) diff --git a/pysollib/stack.py b/pysollib/stack.py index e5b2340b..ade153f5 100644 --- a/pysollib/stack.py +++ b/pysollib/stack.py @@ -57,6 +57,7 @@ __all__ = ['cardsFaceUp', 'SS_FoundationStack', 'RK_FoundationStack', 'AC_FoundationStack', + 'SC_FoundationStack', #'SequenceStack_StackMethods', 'BasicRowStack', 'SequenceRowStack', @@ -2123,6 +2124,27 @@ class AC_FoundationStack(SS_FoundationStack): elif self.cap.dir < 0: return _('Foundation. Build down by alternate color.') else: return _('Foundation. Build by same rank.') +# A SameColor_FoundationStack builds up in rank and alternate color. +# It is used in only a few games. +class SC_FoundationStack(SS_FoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, base_suit=suit) + SS_FoundationStack.__init__(self, x, y, game, ANY_SUIT, **cap) + + def acceptsCards(self, from_stack, cards): + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if self.cards: + # check the color + if cards[0].color != self.cards[-1].color: + return 0 + return 1 + + def getHelp(self): + if self.cap.dir > 0: return _('Foundation. Build up by color.') + elif self.cap.dir < 0: return _('Foundation. Build down by color.') + else: return _('Foundation. Build by same rank.') + # /*********************************************************************** # // Abstract classes for row stacks. diff --git a/pysollib/tile/menubar.py b/pysollib/tile/menubar.py index 78f0541d..c7f8e3fd 100644 --- a/pysollib/tile/menubar.py +++ b/pysollib/tile/menubar.py @@ -37,7 +37,7 @@ __all__ = ['PysolMenubar'] # imports -import math, os, sys, re +import math, os, sys, re, traceback import Tile as Tkinter import tkFileDialog @@ -49,6 +49,7 @@ from pysollib.settings import PACKAGE, WIN_SYSTEM from pysollib.settings import TOP_TITLE from pysollib.settings import SELECT_GAME_MENU from pysollib.settings import USE_FREECELL_SOLVER +from pysollib.settings import DEBUG from pysollib.gamedb import GI from pysollib.actions import PysolMenubarActions @@ -63,6 +64,7 @@ from findcarddialog import connect_game_find_card_dialog, destroy_find_card_dial from solverdialog import connect_game_solver_dialog from tkwrap import MfxRadioMenuItem, MfxCheckMenuItem, StringVar from tkwidget import MfxMessageDialog +from wizarddialog import WizardDialog #from toolbar import TOOLBAR_BUTTONS from tkconst import TOOLBAR_BUTTONS @@ -351,7 +353,7 @@ class PysolMenubar(PysolMenubarActions): if sys.platform == "darwin": m = "Cmd-" if WIN_SYSTEM == "aqua": - applemenu=MfxMenu(self.__menubar, n_("apple")) + applemenu=MfxMenu(self.__menubar, "apple") applemenu.add_command(label=_("&About ")+PACKAGE, command=self.mHelpAbout) menu = MfxMenu(self.__menubar, n_("&File")) @@ -404,6 +406,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") @@ -1356,3 +1362,47 @@ the next time you restart """)+PACKAGE, n = t.capitalize() submenu.add_radiobutton(label=n, variable=self.tkopt.theme, value=t, command=self.mOptTheme) + + 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/tile/solverdialog.py b/pysollib/tile/solverdialog.py index c3cbcc99..fa0270fe 100644 --- a/pysollib/tile/solverdialog.py +++ b/pysollib/tile/solverdialog.py @@ -63,9 +63,9 @@ class SolverDialog(MfxDialog): self.solving_methods = { 'A*': 'a-star', 'Breadth-First Search': 'bfs', - 'Depth-First Search': 'dfs', # default + 'Depth-First Search': 'soft-dfs', # default 'A randomized DFS': 'random-dfs', - '"Soft" DFS': 'soft-dfs', + ##'"Soft" DFS': 'soft-dfs', } self.games = {} # key: gamename; value: gameid @@ -99,8 +99,12 @@ class SolverDialog(MfxDialog): ).grid(row=row, column=0, sticky='ew', padx=2, pady=2) ##sm = self.solving_methods.values() ##sm.sort() - sm = ['A*', 'Breadth-First Search', 'Depth-First Search', - 'A randomized DFS', '"Soft" DFS'] + sm = ['A*', + 'Breadth-First Search', + 'Depth-First Search', + 'A randomized DFS', + ##'"Soft" DFS' + ] cb = Tkinter.Combobox(frame, values=tuple(sm), state='readonly') cb.grid(row=row, column=1, sticky='ew', padx=2, pady=2) cb.current(sm.index('Depth-First Search')) diff --git a/pysollib/tile/tkhtml.py b/pysollib/tile/tkhtml.py index c45bc107..632ba02d 100644 --- a/pysollib/tile/tkhtml.py +++ b/pysollib/tile/tkhtml.py @@ -247,27 +247,30 @@ class HTMLViewer: ##self.defcursor = 'xterm' self.handcursor = "hand2" + frame = Tkinter.Frame(parent) + frame.pack(expand=True, fill='both') + # create buttons button_width = 8 - self.homeButton = Tkinter.Button(parent, text=_("Index"), + self.homeButton = Tkinter.Button(frame, text=_("Index"), width=button_width, command=self.goHome) self.homeButton.grid(row=0, column=0, sticky='w') - self.backButton = Tkinter.Button(parent, text=_("Back"), + self.backButton = Tkinter.Button(frame, text=_("Back"), width=button_width, command=self.goBack) self.backButton.grid(row=0, column=1, sticky='w') - self.forwardButton = Tkinter.Button(parent, text=_("Forward"), + self.forwardButton = Tkinter.Button(frame, text=_("Forward"), width=button_width, command=self.goForward) self.forwardButton.grid(row=0, column=2, sticky='w') - self.closeButton = Tkinter.Button(parent, text=_("Close"), + self.closeButton = Tkinter.Button(frame, text=_("Close"), width=button_width, command=self.destroy) self.closeButton.grid(row=0, column=3, sticky='e') # create text widget - text_frame = Tkinter.Frame(parent) + text_frame = Tkinter.Frame(frame) text_frame.grid(row=1, column=0, columnspan=4, sticky='nsew', padx=1, pady=1) vbar = Tkinter.Scrollbar(text_frame) @@ -282,10 +285,10 @@ class HTMLViewer: vbar["command"] = self.text.yview # statusbar - self.statusbar = HtmlStatusbar(parent, row=2, column=0, columnspan=4) + self.statusbar = HtmlStatusbar(frame, row=2, column=0, columnspan=4) - parent.columnconfigure(2, weight=1) - parent.rowconfigure(1, weight=1) + frame.columnconfigure(2, weight=1) + frame.rowconfigure(1, weight=1) # load images for name, fn in self.symbols_fn.items(): diff --git a/pysollib/tile/wizarddialog.py b/pysollib/tile/wizarddialog.py new file mode 100644 index 00000000..efae86f1 --- /dev/null +++ b/pysollib/tile/wizarddialog.py @@ -0,0 +1,103 @@ +##---------------------------------------------------------------------------## +## +## 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 Tile import * + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct +from pysollib.wizardutil import WizardWidgets + +# Toolkit imports +from tkwidget import MfxDialog +from tkwidget import PysolScale + + +# /*********************************************************************** +# // +# ************************************************************************/ + +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 = Notebook(frame) + notebook.pack(expand=True, fill='both') + + for w in WizardWidgets: + if isinstance(w, basestring): + frame = Frame(notebook) + notebook.add(frame, text=w) + row = 0 + continue + + Label(frame, text=w.label).grid(row=row, column=0) + + if w.widget == 'entry': + w.variable = var = StringVar() + en = Entry(frame, textvariable=var) + en.grid(row=row, column=1, sticky='ew') + 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) + cb.grid(row=row, column=1, sticky='ew', padx=2, pady=2) + elif w.widget == 'spin': + w.variable = var = IntVar() + from_, to = w.values + ##s = Spinbox(frame, textvariable=var, from_=from_, to=to) + s = PysolScale(frame, from_=from_, to=to, resolution=1, + orient='horizontal', + variable=var) + s.grid(row=row, column=1, sticky='ew') + + if w.current_value is None: + var.set(w.default) + else: + var.set(w.current_value) + + row += 1 + + + 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/tk/menubar.py b/pysollib/tk/menubar.py index ebe84642..fbd44c38 100644 --- a/pysollib/tk/menubar.py +++ b/pysollib/tk/menubar.py @@ -348,7 +348,7 @@ class PysolMenubar(PysolMenubarActions): if sys.platform == "darwin": m = "Cmd-" if WIN_SYSTEM == "aqua": - applemenu=MfxMenu(self.__menubar, n_("apple")) + applemenu=MfxMenu(self.__menubar, "apple") applemenu.add_command(label=_("&About ")+PACKAGE, command=self.mHelpAbout) menu = MfxMenu(self.__menubar, n_("&File")) diff --git a/pysollib/tk/solverdialog.py b/pysollib/tk/solverdialog.py index d7a7b9c9..416a2807 100644 --- a/pysollib/tk/solverdialog.py +++ b/pysollib/tk/solverdialog.py @@ -62,9 +62,9 @@ class SolverDialog(MfxDialog): self.solving_methods = { 'A*': 'a-star', 'Breadth-First Search': 'bfs', - 'Depth-First Search': 'dfs', # default + 'Depth-First Search': 'soft-dfs', # default 'A randomized DFS': 'random-dfs', - '"Soft" DFS': 'soft-dfs', + ##'"Soft" DFS': 'soft-dfs', } self.games = {} # key: gamename; value: gameid @@ -102,8 +102,12 @@ class SolverDialog(MfxDialog): ).grid(row=row, column=0, sticky='ew', padx=2, pady=2) ##sm = self.solving_methods.values() ##sm.sort() - sm = ['A*', 'Breadth-First Search', 'Depth-First Search', - 'A randomized DFS', '"Soft" DFS'] + sm = ['A*', + 'Breadth-First Search', + 'Depth-First Search', + 'A randomized DFS', + ##'"Soft" DFS' + ] self.solving_method_var = var = Tkinter.StringVar() var.set('Depth-First Search') om = Tkinter.OptionMenu(frame, var, *sm) diff --git a/pysollib/wizardutil.py b/pysollib/wizardutil.py new file mode 100644 index 00000000..ca086af4 --- /dev/null +++ b/pysollib/wizardutil.py @@ -0,0 +1,348 @@ +##---------------------------------------------------------------------------## +## +## 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. +## +##---------------------------------------------------------------------------## + +import sys, os + +from gamedb import GI, loadGame +from util import * +from stack import * +#from game import Game +from layout import Layout +#from hint import AbstractHint, DefaultHint, CautiousDefaultHint +#from pysoltk import MfxCanvasText + +gettext = _ +n_ = lambda x: x + +# /*********************************************************************** +# // +# ************************************************************************/ + +class WizSetting: + def __init__(self, values_map, default, var_name, + label, widget='menu'): + self.values_map = values_map + self.default = gettext(default) + ##self.values_dict = dict(self.values_map) + self.translate_map = {} + ##self.values = [i[0] for i in self.values_map] + if widget == 'menu': + self.values = [] + for k, v in self.values_map: + t = gettext(k) + self.values.append(t) + self.translate_map[t] = k + assert self.default in self.values + else: + self.values = self.values_map + self.var_name = var_name + self.label = label + self.widget = widget + self.variable = None # Tk variable + self.current_value = None + + +GameName = WizSetting( + values_map = (), + default = 'My Game', + widget = 'entry', + label = _('Name:'), + var_name = 'name', + ) +SkillLevel = WizSetting( + values_map = ((n_('Luck only'), GI.SL_LUCK), + (n_('Mostly luck'), GI.SL_MOSTLY_LUCK), + (n_('Balanced'), GI.SL_BALANCED), + (n_('Mostly skill'), GI.SL_MOSTLY_SKILL), + (n_('Skill only'), GI.SL_SKILL), + ), + default = n_('Balanced'), + label = _('Skill level:'), + var_name = 'skill_level', + ) +NumDecks = WizSetting( + values_map = ((n_('One'), 1), + (n_('Two'), 2), + (n_('Three'), 3), + (n_('Four'), 4)), + default = n_('One'), + label = _('Number of decks:'), + var_name = 'decks', + ) +LayoutType = WizSetting( + values_map = ((n_('FreeCell'), Layout.freeCellLayout), + (n_('Klondike'), Layout.klondikeLayout), + (n_('Gypsy'), Layout.gypsyLayout), + (n_('Harp'), Layout.harpLayout), + ), + default = n_('FreeCell'), + label = _('Layout:'), + var_name = 'layout', + ) +TalonType = WizSetting( + values_map = ((n_('Initial dealing'), InitialDealTalonStack), + (n_('Deal to waste'), WasteTalonStack), + (n_('Deal to rows'), DealRowTalonStack), + ), + default = n_('Initial dealing'), + label = _('Type of talon:'), + var_name = 'talon', + ) +Redeals = WizSetting( + values_map = ((n_('No redeals'), 0), + (n_('One redeal'), 1), + (n_('Two redeals'), 2), + (n_('Unlimited redeals'), -1), + ), + default = n_('No redeals'), + label = _('Number of redeals:'), + 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), + ), + default = n_('Same suit'), + label = _('Type:'), + var_name = 'found_type', + ) +FoundBaseCard = WizSetting( + values_map = ((n_('Ace'), ACE), (n_('King'), KING)), + default = n_('Ace'), + label = _('Base card:'), + var_name = 'found_base_card', + ) +FoundDir = WizSetting( + values_map = ((n_('Up'), 1), (n_('Down'), -1)), + default = n_('Up'), + label = _('Direction:'), + var_name = 'found_dir', + ) +FoundWrap = WizSetting( + values_map = ((n_('Yes'), True), (n_('No'), False)), + default = n_('No'), + label = _('Wrapping:'), + var_name = 'found_wrap', + ) +FoundMaxMove = WizSetting( + values_map = ((n_('No move'), 0,), (n_('One card'), 1)), + default = n_('One card'), + label = _('Max move cards:'), + var_name = 'found_max_move', + ) +RowsNum = WizSetting( + values_map = (1, 20), + default = 8, + widget = 'spin', + label = _('Number of rows:'), + 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), + ), + default = n_('Alternate color'), + label = _('Type:'), + var_name = 'rows_type', + ) +RowsBaseCard = WizSetting( + values_map = ((n_('Ace'), ACE), + (n_('King'), KING), + (n_('Any'), ANY_RANK), + (n_('No'), NO_RANK), + ), + default = n_('Any'), + label = _('Base card:'), + var_name = 'rows_base_card', + ) +RowsDir = WizSetting( + values_map = ((n_('Up'), 1), (n_('Down'), -1)), + default = n_('Down'), + label = _('Direction:'), + var_name = 'rows_dir', + ) +RowsWrap = WizSetting( + values_map = ((n_('Yes'), True), (n_('No'), False)), + default = n_('No'), + label = _('Wrapping:'), + var_name = 'rows_wrap', + ) +RowsMaxMove = WizSetting( + values_map = ((n_('One card'), 1), (n_('Unlimited'), UNLIMITED_MOVES)), + default = n_('Unlimited'), + label = _('Max move cards:'), + var_name = 'rows_max_move', + ) +ReservesNum = WizSetting( + values_map = (0, 20), + default = 4, + widget = 'spin', + 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', + ) +DealType = WizSetting( + values_map = ((n_('Triangle'), 'triangle'), + (n_('Rectangle'), 'rectangle'), + ), + default = n_('Rectangle'), + label = _('Type:'), + var_name = 'deal_type', + ) +DealFaceUp = WizSetting( + values_map = ((n_('Top cards'), 'top'), (n_('All cards'), 'all')), + default = n_('All cards'), + label = _('Face-up:'), + var_name = 'deal_faceup', + ) +DealMaxRows = WizSetting( + values_map = (0, 20), + default = 7, + widget = 'spin', + label = _('Deal to rows:'), + var_name = 'deal_to_rows', + ) +DealToReseves = WizSetting( + values_map = (0, 20), + default = 0, + widget = 'spin', + label = _('Deal ro reserves:'), + var_name = 'deal_to_reserves', + ) + +WizardWidgets = ( + _('General'), + GameName, + SkillLevel, + NumDecks, + LayoutType, + _('Talon'), + TalonType, + Redeals, + _('Foundations'), + FoundType, + FoundBaseCard, + FoundDir, + FoundWrap, + FoundMaxMove, + _('Tableau'), + RowsNum, + RowsType, + RowsBaseCard, + RowsDir, + RowsWrap, + RowsMaxMove, + _('Reserves'), + ReservesNum, + ReservesType, + _('Initial dealing'), + DealType, + DealFaceUp, + DealMaxRows, + DealToReseves, + ) + + +def write_game(app, game=None): + + if game is None: + # new game + d = app.dn.plugins + ls = os.listdir(d) + n = 1 + while True: + fn = os.path.join(d, 'customgame%d.py' % n) # file name + mn = 'customgame%d' % n # module name + gameid = 200000+n + if not os.path.exists(fn): + break + n += 1 + check_game = True + else: + # edit current game + fn = game.SETTINGS['file'] + fn = os.path.join(app.dn.plugins, fn) + mn = game.__module__ + gameid = game.SETTINGS['gameid'] + n = gameid-200000 + check_game = False + + ##print '===>', fn + fd = open(fn, 'w') + + fd.write('''\ +## THIS FILE WAS GENERATED AUTOMATICALLY BY SOLITAIRE WIZARD +## DO NOT EDIT + +from pysollib.customgame import CustomGame, registerCustomGame + +class MyCustomGame(CustomGame): + WIZARD_VERSION = 1 + SETTINGS = { +''') + + for w in WizardWidgets: + if isinstance(w, basestring): + continue + v = w.variable.get() + if w.widget == 'menu': + v = w.translate_map[v] + if isinstance(v, int): + fd.write(" '%s': %i,\n" % (w.var_name, v)) + else: + 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]) + + fd.write('''\ + } + +registerCustomGame(MyCustomGame) +''') + fd.close() + + loadGame(mn, fn, check_game=check_game) + + return gameid + +def reset_wizard(game): + s = game.SETTINGS + for w in WizardWidgets: + if isinstance(w, basestring): + continue + v = s[w.var_name] + if w.widget == 'menu': + v = gettext(v) + w.current_value = v + +