#!/usr/bin/env python # -*- mode: python; coding: utf-8; -*- # ---------------------------------------------------------------------------## # # Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer # Copyright (C) 2003 Mt. Hood Playing Card Co. # Copyright (C) 2005-2009 Skomoroh # # 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 3 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. If not, see . # # ---------------------------------------------------------------------------## import os import re import sys import traceback from pickle import UnpicklingError import pysollib.app_stat from pysollib.actions import PysolMenubar from pysollib.actions import PysolToolbar from pysollib.app_stat_result import GameStatResult from pysollib.app_statistics import Statistics from pysollib.cardsetparser import read_cardset_config from pysollib.gamedb import GAME_DB, GI, loadGame from pysollib.help import (destroy_help_html, help_about, raise_help_html, unraise_help_html) from pysollib.images import Images, SubsampledImages from pysollib.mfxutil import Struct, destruct from pysollib.mfxutil import USE_PIL from pysollib.mfxutil import getprefdir, getusername from pysollib.mfxutil import latin1_normalize, print_err from pysollib.mfxutil import pickle, unpickle from pysollib.mygettext import _ from pysollib.options import Options from pysollib.pysolrandom import PysolRandom, construct_random from pysollib.pysoltk import HTMLViewer from pysollib.pysoltk import MfxDialog, MfxExceptionDialog, MfxMessageDialog from pysollib.pysoltk import MfxScrolledCanvas, TclError from pysollib.pysoltk import PysolProgressBar from pysollib.pysoltk import PysolStatusbar from pysollib.pysoltk import SelectCardsetDialogWithPreview from pysollib.pysoltk import SelectDialogTreeData from pysollib.pysoltk import destroy_find_card_dialog from pysollib.pysoltk import destroy_full_picture_dialog from pysollib.pysoltk import loadImage, wm_withdraw from pysollib.pysoltk import raise_find_card_dialog from pysollib.pysoltk import raise_full_picture_dialog from pysollib.pysoltk import raise_solver_dialog from pysollib.pysoltk import unraise_find_card_dialog from pysollib.pysoltk import unraise_full_picture_dialog from pysollib.pysoltk import unraise_solver_dialog from pysollib.resource import CSI, CardsetManager from pysollib.resource import Music, MusicManager from pysollib.resource import Sample, SampleManager from pysollib.resource import Tile, TileManager from pysollib.settings import DEBUG from pysollib.settings import PACKAGE, VERSION_TUPLE # , WIN_SYSTEM from pysollib.settings import TOOLKIT from pysollib.util import IMAGE_EXTENSIONS from pysollib.winsystems import TkSettings if TOOLKIT == 'tk': from pysollib.ui.tktile.solverdialog import destroy_solver_dialog else: from pysollib.pysoltk import destroy_solver_dialog if TOOLKIT == 'kivy': import logging _GameStatResult = GameStatResult GameStat = pysollib.app_stat.GameStat # ************************************************************************ # * Application # * This is the glue between the toplevel window and a Game. # * Also handles all global resources. # ************************************************************************ image_ext_re_str = "(" + "|".join( ["(?:\\" + e + ")" for e in IMAGE_EXTENSIONS]) + ")$" image_ext_re = re.compile(image_ext_re_str, re.I | re.U) class Application: def __init__(self): self.gdb = GAME_DB self.opt = Options() self.startup_opt = self.opt.copy() self.stats = Statistics() self.splashscreen = 1 # visual components self.top = None # the root toplevel window self.top_bg = None # default background self.top_cursor = None # default cursor self.menubar = None self.toolbar = None self.canvas = None # MfxCanvas self.scrolled_canvas = None # MfxScrolledCanvas self.statusbar = None # self.game = None self.dataloader = None self.audio = None self.images = None self.subsampled_images = None self.gimages = Struct( # global images demo=[], # demo logos pause=[], # pause logos logos=[], redeal=[], ) # self.progress_bg = None 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() self.music_manager = MusicManager() self.music_playlist = [] self.intro = Struct( progress=None, # progress bar ) # directory names config = os.path.normpath(getprefdir(PACKAGE)) self.dn = Struct( config=config, plugins=os.path.join(config, "plugins"), savegames=os.path.join(config, "savegames"), boards=os.path.join(config, "boards"), maint=os.path.join(config, "maint"), # debug ) for k, v in self.dn.__dict__.items(): # if os.name == "nt": # v = os.path.normcase(v) v = os.path.normpath(v) self.dn.__dict__[k] = v # file names self.fn = Struct( opt=os.path.join(self.dn.config, "options.dat"), opt_cfg=os.path.join(self.dn.config, "options.cfg"), stats=os.path.join(self.dn.config, "statistics.dat"), holdgame=os.path.join(self.dn.config, "holdgame.dat"), comments=os.path.join(self.dn.config, "comments.dat"), ) for k, v in self.dn.__dict__.items(): if os.name == "nt": v = os.path.normcase(v) v = os.path.normpath(v) self.fn.__dict__[k] = v # random generators self.gamerandom = PysolRandom() self.miscrandom = PysolRandom() # player player = getusername() if not player: player = "unknown" player = player[:30] self.opt.player = player # misc self.nextgame = Struct( id=0, # start this game random=None, # use this random generator loadedgame=None, # data for loaded game startdemo=0, # start demo ? cardset=None, # use this cardset holdgame=0, # hold this game on exit ? bookmark=None, # goto this bookmark (load new cardset) ) self.commandline = Struct( loadgame=None, # load a game ? game=None, gameid=None, deal=None, ) self.demo_counter = 0 # the PySol mainloop def mainloop(self): try: approc = self.mainproc() # setup process approc.send(None) # and go except StopIteration: pass def gameproc(self): while True: logging.info('App: gameproc waiting for game to start') (id_, random) = yield logging.info('App: game started {},{}'.format(id_, random)) self.runGame(id_, random) def _load_held_or_saved_game(self, tmpgame): """docstring for _load_held_or_saved_game""" if self.opt.game_holded > 0 and not self.nextgame.loadedgame: game = None try: game = tmpgame._loadGame(self.fn.holdgame, self) except Exception: traceback.print_exc() game = None if game: if game.id == self.opt.game_holded and game.gstats.holded: game.gstats.loaded = game.gstats.loaded - 1 game.gstats.holded = 0 self.nextgame.loadedgame = game else: # not a holded game game.destruct() destruct(game) def _main_loop(self): """docstring for _main_loop""" try: # this is the mainloop while 1: assert self.cardset is not None id_, random = self.nextgame.id, self.nextgame.random self.nextgame.id, self.nextgame.random = 0, None try: if TOOLKIT == 'kivy': self.gproc.send((id_, random)) logging.info('App: sent for game to start') yield logging.info('App: game proc stopped') else: self.runGame(id_, random) except Exception: # try Klondike if current game fails if id_ == 2: raise # internal error? if DEBUG: raise traceback.print_exc() self.nextgame.id = 2 self.freeGame() continue if self.nextgame.holdgame: assert self.nextgame.id <= 0 try: self.game.gstats.holded = 1 self.game._saveGame(self.fn.holdgame) self.opt.game_holded = self.game.id except Exception: traceback.print_exc() pass self.wm_save_state() # save game geometry geom = (self.canvas.winfo_width(), self.canvas.winfo_height()) if self.opt.save_games_geometry and not self.opt.wm_maximized: self.opt.games_geometry[self.game.id] = geom self.opt.game_geometry = geom self.freeGame() # if self.nextgame.id <= 0: break # load new cardset if self.nextgame.cardset is not self.cardset: self.loadCardset( self.nextgame.cardset, id=self.nextgame.id, update=7+256) else: self.requestCompatibleCardsetType(self.nextgame.id) except Exception: traceback.print_exc() pass finally: # hide main window self.wm_withdraw() # destroy_help_html() destroy_find_card_dialog() destroy_full_picture_dialog() destroy_solver_dialog() # update options self.opt.last_gameid = id_ # save options try: self.saveOptions() except Exception: traceback.print_exc() pass # save statistics try: self.saveStatistics() except Exception: traceback.print_exc() pass # shut down audio try: self.audio.destroy() except Exception: traceback.print_exc() pass if TOOLKIT == 'kivy': self.top.quit() while True: logging.info('App: mainloop end position') yield def mainproc(self): # copy startup options self.startup_opt = self.opt.copy() # try to load statistics try: self.loadStatistics() except Exception: traceback.print_exc() pass # Under normal circumstances, this won't trigger. # But if the config has been incorrectly edited or # otherwise corrupted, this can prevent a crash. if 0 < self.opt.game_holded != self.opt.last_gameid: self.opt.last_gameid = self.opt.game_holded # startup information if self.getGameClass(self.opt.last_gameid): self.nextgame.id = self.opt.last_gameid elif self.opt.last_gameid in GI.PROTECTED_GAMES: self.nextgame.id = GI.PROTECTED_GAMES.get(self.opt.last_gameid) # load a holded or saved game tmpgame = self.constructGame(self.gdb.getGamesIdSortedByName()[0]) self._load_held_or_saved_game(tmpgame) if not self.nextgame.loadedgame: if self.commandline.loadgame: try: self.nextgame.loadedgame = tmpgame._loadGame( self.commandline.loadgame, self) self.nextgame.loadedgame.gstats.holded = 0 self.nextgame.id = self.nextgame.loadedgame.id except Exception: traceback.print_exc() self.nextgame.loadedgame = None elif self.commandline.game is not None: gameid = self.gdb.getGameByName(self.commandline.game) if gameid is None: print_err(_("can't find game: %(game)s") % { 'game': self.commandline.game}) sys.exit(-1) else: self.nextgame.id = gameid deal = self.commandline.deal self.nextgame.random = \ None if deal is None else construct_random(deal) elif self.commandline.gameid is not None: self.nextgame.id, self.nextgame.random = \ self.commandline.gameid, None self.opt.game_holded = 0 tmpgame.destruct() destruct(tmpgame) # # widgets # # create the menubar if self.intro.progress: self.intro.progress.update(step=1) self.menubar = PysolMenubar(self, self.top, progress=self.intro.progress) # create the statusbar(s) self.statusbar = PysolStatusbar(self.top) self.statusbar.show(self.opt.statusbar) for w, v in self.opt.statusbar_vars.items(): self.statusbar.config(w, v) # create the canvas self.scrolled_canvas = MfxScrolledCanvas(self.top, propagate=True) self.canvas = self.scrolled_canvas.canvas padx, pady = TkSettings.canvas_padding self.scrolled_canvas.grid(row=1, column=1, sticky='nsew', padx=padx, pady=pady) self.top.grid_columnconfigure(1, weight=1) self.top.grid_rowconfigure(1, weight=1) self.setTile(self.tabletile_index, force=True) # create the toolbar dirname = self.getToolbarImagesDir() self.toolbar = PysolToolbar(self.top, self.menubar, dir=dirname, size=self.opt.toolbar_size, relief=self.opt.toolbar_relief, compound=self.opt.toolbar_compound) self.toolbar.show(self.opt.toolbar) if TOOLKIT == 'tk': for w, v in self.opt.toolbar_vars.items(): self.toolbar.config(w, v) # if self.intro.progress: self.intro.progress.update(step=1) # if TOOLKIT == 'kivy': self.gproc = self.gameproc() self.gproc.send(None) return self._main_loop() def runGame(self, id_, random=None): self.top.connectApp(self) # create game instance g = self.getGameClass(id_) if g is None: id_ = 2 # start Klondike as default game random = None g = self.getGameClass(id_) if g is None: # start first available game id_ = self.gdb.getGamesIdSortedByName()[0] g = self.getGameClass(id_) gi = self.getGameInfo(id_) assert gi is not None and gi.id == id_ self.game = self.constructGame(id_) self.gdb.setSelected(id_) self.game.busy = 1 # create stacks and layout self.game.create(self) # connect with game self.menubar.connectGame(self.game) if self.toolbar: # ~ self.toolbar.connectGame(self.game) self.game.updateStatus(player=self.opt.player) # update "Recent games" menubar entry if id_ in self.opt.recent_gameid: self.opt.recent_gameid.remove(id_) self.opt.recent_gameid.insert(0, id_) del self.opt.recent_gameid[self.opt.num_recent_games:] self.menubar.updateRecentGamesMenu(self.opt.recent_gameid) self.menubar.updateFavoriteGamesMenu() # hide/show "Shuffle" button self.toolbar.config( 'shuffle', self.opt.toolbar_vars['shuffle'] and self.game.canShuffle()) # delete intro progress bar if self.intro.progress: self.intro.progress.destroy() destruct(self.intro.progress) self.intro.progress = None # prepare game autoplay = 0 if self.nextgame.loadedgame is not None: self.stats.gameid_balance = 0 self.game.restoreGame(self.nextgame.loadedgame) destruct(self.nextgame.loadedgame) elif self.nextgame.bookmark is not None: self.game.restoreGameFromBookmark(self.nextgame.bookmark) else: self.stats.gameid_balance = 0 self.game.newGame(random=random, autoplay=0) autoplay = 1 self.nextgame.loadedgame = None self.nextgame.bookmark = None # splash screen if self.opt.splashscreen and self.splashscreen > 0: status = help_about(self, timeout=20000, sound=0) if status == 2: # timeout - start a demo if autoplay: self.nextgame.startdemo = 1 self.splashscreen = 0 # start demo/autoplay if self.nextgame.startdemo: self.nextgame.startdemo = 0 self.game.startDemo() self.game.createDemoInfoText() elif autoplay: self.game.autoPlay() self.game.stats.player_moves = 0 # enter the Tk mainloop self.game.busy = 0 self.top.mainloop() # free game def freeGame(self): # disconnect from game self.menubar.connectGame(None) self.toolbar.connectGame(None) # clean up the canvas self.canvas.deleteAllItems() self.canvas.update_idletasks() # destruct the game if self.game: self.game.destruct() destruct(self.game) self.game = None self.top.connectApp(None) # # UI support # def wm_save_state(self): if self.top: s = self.top.wm_state() # print "wm_save_state", s if s == "zoomed": # Windows only self.opt.wm_maximized = True elif s == "normal": self.opt.wm_maximized = False def wm_withdraw(self): if self.intro.progress: self.intro.progress.destroy() destruct(self.intro.progress) self.intro.progress = None if self.top: wm_withdraw(self.top) self.top.busyUpdate() def wm_toggle_fullscreen(self): self.opt.wm_fullscreen = not self.opt.wm_fullscreen self.top.attributes("-fullscreen", self.opt.wm_fullscreen) # Topmost dialogs need to be reset when toggling fullscreen. self.raiseAll() self.top.attributes('-topmost', False) def raiseAll(self): raise_find_card_dialog(self.game) raise_full_picture_dialog(self.game) raise_solver_dialog(self.game) raise_help_html(self.game) def unraiseAll(self): unraise_find_card_dialog() unraise_full_picture_dialog() unraise_solver_dialog() unraise_help_html() def loadImages1(self): # load dialog images dirname = os.path.join("images", "logos") self.gimages.logos = [] for f in ("joker07_40_774", "joker08_40_774", "joker07_50_774", "joker08_50_774", "joker11_100_774", "joker10_100",): self.gimages.logos.append(self.dataloader.findImage(f, dirname)) dirname = os.path.join('images', 'dialog', self.opt.dialog_icon_style) for f in ('error', 'info', 'question', 'warning'): fn = self.dataloader.findImage(f, dirname) im = loadImage(fn) MfxMessageDialog.img[f] = im # load button images MfxDialog.button_img = {} if TOOLKIT == 'tk' and self.opt.button_icon_style != 'none': dirname = os.path.join('images', 'buttons', self.opt.button_icon_style) for n, f in ( (_('&OK'), 'ok'), (_('&Select'), 'ok'), (_('&Nice'), 'ok'), (_('&Enjoy'), 'ok'), (_("&Great"), 'ok'), (_("&Cool"), 'ok'), (_("&Yeah"), 'ok'), (_("&Wow"), 'ok'), (_("&Oh well"), 'ok'), (_("&That's life"), 'ok'), (_("&Hmm"), 'ok'), (_('&Cancel'), 'cancel'), (_('&Close'), 'cancel'), (_("&Apply"), 'apply'), (_("&Start"), 'apply'), (_('&New'), 'new'), (_('&New game'), 'new'), (_('&Back to game'), 'back'), (_('&Reset...'), 'reset'), (_('&Restart'), 'reset'), (_('&Rules'), 'help'), (_('&Info...'), 'help'), (_('&Credits'), 'help'), (_('&Next number'), 'next'), (_('&Play'), 'next'), (_('&Play this game'), 'next'), (_('C&lear'), 'clear'), (_('&Solid color...'), 'color'), (_('&Save to file'), 'save'), (_('&Statistics...'), 'statistics'), ): fn = self.dataloader.findImage(f, dirname) im = loadImage(fn) MfxDialog.button_img[n] = im def loadImages2(self): # load canvas images dirname = os.path.join("images", "redealicons", self.opt.redeal_icon_style) # for f in ("noredeal", "redeal",): self.gimages.redeal = [] for f in ("stopsign", "redeal",): self.gimages.redeal.append(self.dataloader.findImage(f, dirname)) dirname = os.path.join("images", "demo", self.opt.demo_logo_style) self.gimages.demo = [] foundall = False count = 0 while not foundall: count += 1 try: self.gimages.demo.append(self.dataloader.findImage("demo" + ("%02d" % (count,)), dirname)) except OSError: foundall = True dirname = os.path.join("images", "pause", self.opt.pause_text_style) self.gimages.pause = [] foundall = False count = 0 while not foundall: count += 1 try: self.gimages.pause.append(self.dataloader.findImage("pause" + ("%02d" % (count,)), dirname)) except OSError: foundall = True # dirname = os.path.join("images", "stats") # for f in ("barchart",): # self.gimages.stats.append(self.dataloader.findImage(f, dirname)) def loadImages3(self): # load treeview images SelectDialogTreeData.img = [] dirname = os.path.join('images', 'tree', self.opt.tree_icon_style) for f in ('folder', 'openfolder', 'node', 'emptynode'): fn = self.dataloader.findImage(f, dirname) im = loadImage(fn) SelectDialogTreeData.img.append(im) # load htmlviewer images dirname = os.path.join('images', 'htmlviewer') fn = self.dataloader.findImage('disk', dirname) HTMLViewer.symbols_fn['disk'] = fn def loadImages4(self): # load all remaining images for k, v in self.gimages.__dict__.items(): if isinstance(v, list): for i in range(len(v)): if isinstance(v[i], str): v[i] = loadImage(v[i]) if self.intro.progress: self.intro.progress.update(step=1) self.gimages.__dict__[k] = tuple(v) def _getImagesDir(self, *dirs, **kwargs): check = kwargs.get('check', True) dirs = [str(d) for d in dirs] # XXX: don't use unicode d = os.path.join(self.dataloader.dir, 'images', *dirs) if check: if os.path.exists(d): return d return None return d def getFindCardImagesDir(self): return self._getImagesDir('cards') def getToolbarImagesDir(self): if self.opt.toolbar_size == 2: size = 'xlarge' elif self.opt.toolbar_size == 1: size = 'large' else: size = 'small' style = self.opt.toolbar_style if TOOLKIT == 'kivy': size = 'xlarge' style = 'remix light' d = self._getImagesDir('toolbar', style, size) if d: return d return self._getImagesDir('toolbar', 'default', size, check=False) def setTile(self, i, scaling=-1, force=0): if scaling == -1: scaling = self.opt.tabletile_scale_method if self.scrolled_canvas.setTile(self, i, scaling, force): tile = self.tabletile_manager.get(i) if i == 0: self.opt.colors['table'] = tile.color self.opt.tabletile_name = None else: self.opt.tabletile_name = tile.name self.tabletile_index = i self.tabletile_manager.setSelected(i) return True return False def getFont(self, name): return self.opt.fonts.get(name) # # search logic # def checkSearchString(self, search_string, check_string): terms = search_string.split() if len(terms) == 0: return True for term in terms: if term.upper() not in check_string.upper(): return False return True # # cardset # def updateCardset(self, id=0, update=7): cs = self.images.cs self.cardset = cs self.nextgame.cardset = cs # update settings self.cardset_manager.setSelected(cs.index) # update options self.images.setNegative(self.opt.negative_bottom) self.subsampled_images.setNegative(self.opt.negative_bottom) if update & 1: self.opt.cardset[0][0] = (cs.name, cs.backname) if update & 2: self.opt.cardset[cs.si.type][cs.si.subtype] = (cs.name, cs.backname) gi = self.getGameInfo(id) if gi: if update & 256: try: del self.opt.cardset[(1, gi.id)][gi.subcategory] except KeyError: pass t = self.checkCompatibleCardsetType(gi, cs) if not t[1]: if update & 4: self.opt.cardset[gi.category][gi.subcategory] = \ (cs.name, cs.backname) if update & 8: self.opt.cardset[(1, gi.id)][gi.subcategory] = \ (cs.name, cs.backname) # from pprint import pprint; pprint(self.opt.cardset) def loadCardset(self, cs, id=0, update=7, progress=None, tocache=False, noprogress=False): # print 'loadCardset', cs.ident r = 0 if cs is None or cs.error: return 0 if cs is self.cardset: if not tocache: self.updateCardset(id, update=update) return 1 # cache carsets # self.cardsets_cache: # key: Cardset.type # value: (Cardset.ident, Images, SubsampledImages) c = self.cardsets_cache.get(cs.type) if c: c2 = c.get(cs.subtype) if c2 and c2[0] == cs.ident: # print 'load from cache', c self.images, self.subsampled_images = c2[1], c2[2] if not tocache: self.updateCardset(id, update=update) if self.menubar is not None: self.menubar.updateBackgroundImagesMenu() return 1 # if progress is None and not noprogress: self.wm_save_state() self.wm_withdraw() title = _("Loading cardset %s...") % cs.name color = self.opt.colors['table'] if self.tabletile_index > 0: color = "#008200" progress = PysolProgressBar(self, self.top, title=title, color=color, images=self.progress_images) images = Images(self.dataloader, cs) images.cardset_bottoms = self.opt.use_cardset_bottoms try: if not images.load(app=self, progress=progress): raise Exception("Invalid or damaged cardset") simages = SubsampledImages(images) if tocache: simages.setNegative(self.opt.negative_bottom) # The save cardsets option is deprecated, and its existence # directly conflicts with the ability to allow previews of # other cardset types. # if self.opt.save_cardsets: c = self.cardsets_cache.get(cs.type) if c: c2 = c.get(cs.subtype) if c2: # c2[1].destruct() destruct(c2[1]) self.cardsets_cache[cs.type] = {} self.cardsets_cache[cs.type][cs.subtype] = (cs.ident, images, simages) if not tocache: # elif self.images is not None: # # self.images.destruct() # destruct(self.images) # if self.cardset: if self.cardset.ident != cs.ident: if self.cardset.type == cs.type: # clear saved games geometry self.opt.games_geometry = {} # update self.images = images self.subsampled_images = simages self.updateCardset(id, update=update) r = 1 except (Exception, TclError, UnpicklingError) as ex: traceback.print_exc() cs.error = 1 # restore settings self.nextgame.cardset = self.cardset if self.cardset: self.cardset_manager.setSelected(self.cardset.index) # images.destruct() destruct(images) MfxExceptionDialog( self.top, ex, title=_("Cardset load error"), text=_("Error while loading cardset")) self.intro.progress = progress if r and not tocache and self.menubar is not None: self.menubar.updateBackgroundImagesMenu() return r def checkCompatibleCardsetType(self, gi, cs): assert gi is not None cs_type = "None" cs_subtype = "None" gc = gi.category gs = gi.subcategory if cs is not None: cs_type = cs.si.type cs_subtype = cs.si.subtype t0, t1 = None, None if gc == GI.GC_FRENCH: t0 = "French" if cs_type not in (CSI.TYPE_FRENCH, # CSI.TYPE_TAROCK, ): t1 = t0 if (cs_subtype == CSI.SUBTYPE_NONE and gs == CSI.SUBTYPE_JOKER_DECK): t1 = t0 elif gc == GI.GC_HANAFUDA: t0 = "Hanafuda" if cs_type not in (CSI.TYPE_HANAFUDA,): t1 = t0 elif gc == GI.GC_TAROCK: t0 = "Tarock" if cs_type not in (CSI.TYPE_TAROCK,): t1 = t0 elif gc == GI.GC_MAHJONGG: t0 = "Mahjongg" if cs_type not in (CSI.TYPE_MAHJONGG,): t1 = t0 elif gc == GI.GC_HEXADECK: t0 = "Hex A Deck" if cs_type not in (CSI.TYPE_HEXADECK,): t1 = t0 elif gc == GI.GC_MUGHAL_GANJIFA: t0 = "Mughal Ganjifa" if cs_type not in (CSI.TYPE_MUGHAL_GANJIFA, CSI.TYPE_NAVAGRAHA_GANJIFA, CSI.TYPE_DASHAVATARA_GANJIFA,): t1 = t0 elif gc == GI.GC_NAVAGRAHA_GANJIFA: t0 = "Navagraha Ganjifa" if cs_type not in (CSI.TYPE_NAVAGRAHA_GANJIFA, CSI.TYPE_DASHAVATARA_GANJIFA,): t1 = t0 elif gc == GI.GC_DASHAVATARA_GANJIFA: t0 = "Dashavatara Ganjifa" if cs_type not in (CSI.TYPE_DASHAVATARA_GANJIFA,): t1 = t0 elif gc == GI.GC_TRUMP_ONLY: t0 = "Trump only" if cs_type not in (CSI.TYPE_TRUMP_ONLY,): t1 = t0 elif len(cs.trumps) < gi.ncards: # not enough cards t1 = t0 elif gc == GI.GC_MATCHING: t0 = "Matching" if cs.ncards < (gi.ncards / 2): # not enough cards t1 = t0 elif gc == GI.GC_PUZZLE: t0 = "Puzzle" if cs_type not in (CSI.TYPE_PUZZLE,) or cs_subtype != gs: t1 = t0 elif gc == GI.GC_ISHIDO: t0 = "Ishido" if cs_type not in (CSI.TYPE_ISHIDO,): t1 = t0 else: # we should not come here t0 = t1 = "Unknown" return t0, t1 def getCompatibleCardset(self, gi, cs, trychange=True): if gi is None: return cs, 1, None t = self.checkCompatibleCardsetType(gi, cs) # try current if cs: if not t[1]: return cs, 1, t if trychange: # try by gameid / category for key, flag in (((1, gi.id), 8), (gi.category, 4)): c = self.opt.cardset.get(key) c2 = None if c: c2 = c.get(gi.subcategory) if not c2 or len(c2) != 2: continue cs = self.cardset_manager.getByName(c2[0]) if not cs: continue t = self.checkCompatibleCardsetType(gi, cs) if not t[1]: cs.updateCardback(backname=c2[1]) return cs, flag, t # ask return None, 0, t def requestCompatibleCardsetType(self, id, progress=None): gi = self.getGameInfo(id) # cs, cs_update_flag, t = self.getCompatibleCardset(gi, self.cardset) if cs is self.cardset: return 0 if cs is not None: self.loadCardset(cs, update=1, progress=progress) return 1 self.requestCompatibleCardsetTypeDialog(self.cardset, gi, t) cs = self.__selectCardsetDialog(t) if cs is None: return -1 self.loadCardset(cs, id=id, progress=progress) return 1 def requestCompatibleCardsetTypeDialog(self, cardset, gi, t): MfxMessageDialog( self.top, title=_("Incompatible cardset"), bitmap="warning", text=_('''The currently selected cardset %(cardset)s is not compatible with the game %(game)s Please select a %(correct_type)s type cardset. ''') % {'cardset': cardset.name, 'game': gi.name, 'correct_type': t[0]}, strings=(_("&OK"),), default=0) def selectCardset(self, title, key): d = SelectCardsetDialogWithPreview( self.top, title=title, app=self, manager=self.cardset_manager, key=key) cs = self.cardset_manager.get(d.key) if d.status != 0 or d.button != 0 or d.key < 0 or cs is None: return None changed = False if USE_PIL: if (self.opt.scale_x, self.opt.scale_y, self.opt.auto_scale, self.opt.spread_stacks, self.opt.preserve_aspect_ratio) != \ d.scale_values or \ (cs.CARD_XOFFSET, cs.CARD_YOFFSET) != d.cardset_values: changed = True if d.key == self.cardset.index and not changed: return None if USE_PIL: (self.opt.scale_x, self.opt.scale_y, self.opt.auto_scale, self.opt.spread_stacks, self.opt.preserve_aspect_ratio) = d.scale_values if not self.opt.auto_scale: self.images.resize(self.opt.scale_x, self.opt.scale_y, resample=self.opt.resampling) if d.cardset_values: cs.CARD_XOFFSET, cs.CARD_YOFFSET = d.cardset_values self.opt.offsets[cs.ident] = d.cardset_values self.images.setOffsets() return cs def __selectCardsetDialog(self, t): cs = self.selectCardset( _("Please select a %s type cardset") % t[0], self.cardset.index) return cs # # load & save options, and statistics # def loadOptions(self): self.opt.setDefaults(self.top) if os.path.exists(self.fn.opt): # for backwards compatibility opt = unpickle(self.fn.opt) if opt: self.opt.__dict__.update(opt.__dict__) try: os.remove(self.fn.opt) except Exception: pass self.opt.load(self.fn.opt_cfg) self.opt.setConstants() def loadStatistics(self): if not os.path.exists(self.fn.stats): return stats = unpickle(self.fn.stats) if stats: # print "loaded:", stats.__dict__ self.stats.__dict__.update(stats.__dict__) # start a new session self.stats.session_games = {} self.stats.session_balance = {} self.stats.gameid_balance = 0 def __saveObject(self, obj, fn): obj.version_tuple = VERSION_TUPLE obj.saved += 1 pickle(obj, fn, protocol=-1) def saveOptions(self): self.opt.save(self.fn.opt_cfg) def saveStatistics(self): self.__saveObject(self.stats, self.fn.stats) # # access games database # def constructGame(self, id): gi = self.gdb.get(id) if gi is None: raise Exception("Unknown game (id %d)" % id) return gi.gameclass(gi) def getGamesIdSortedById(self): return self.gdb.getGamesIdSortedById() def getGamesIdSortedByName(self, player=''): return self.gdb.getGamesIdSortedByName() ## def getGamesIdSortedByPlayed(self, player=''): if player == '': player = self.opt.player def _key(a): wa, la, ta, ma = self.stats.getFullStats(player, a) return wa+la games = list(self.gdb.getGamesIdSortedByName()) games.sort(key=_key) return games[::-1] def getGamesIdSortedByWon(self, player=''): if player == '': player = self.opt.player def _key(a): wa, la, ta, ma = self.stats.getFullStats(player, a) return wa games = list(self.gdb.getGamesIdSortedByName()) games.sort(key=_key) return games[::-1] def getGamesIdSortedByLost(self, player=''): if player == '': player = self.opt.player def _key(a): wa, la, ta, ma = self.stats.getFullStats(player, a) return la games = list(self.gdb.getGamesIdSortedByName()) games.sort(key=_key) return games[::-1] def getGamesIdSortedByPercent(self, player=''): if player == '': player = self.opt.player def _key(a): wa, la, ta, ma = self.stats.getFullStats(player, a) return float(wa)/(1 if wa+la == 0 else wa+la) games = list(self.gdb.getGamesIdSortedByName()) games.sort(key=_key) return games[::-1] def getGamesIdSortedByPlayingTime(self, player=''): if player == '': player = self.opt.player def _key(a): wa, la, ta, ma = self.stats.getFullStats(player, a) return ta games = list(self.gdb.getGamesIdSortedByName()) games.sort(key=_key) return games[::-1] def getGamesIdSortedByMoves(self, player=''): if player == '': player = self.opt.player def _key(a): wa, la, ta, ma = self.stats.getFullStats(player, a) return ma games = list(self.gdb.getGamesIdSortedByName()) games.sort(key=_key) return games[::-1] def getGameInfo(self, id): return self.gdb.get(id) def getGameClass(self, id): gi = self.gdb.get(id) if gi is None: return None return gi.gameclass def getGameTitleName(self, id): gi = self.gdb.get(id) if gi is None: return None return gi.name def getGameMenuitemName(self, id): gi = self.gdb.get(id) if gi is None: return None return gi.short_name def getGameRulesFilename(self, id): gi = self.gdb.get(id) if gi is None: return None if gi.rules_filename is not None: return gi.rules_filename n = latin1_normalize(gi.en_name) + '.html' # english name f = os.path.join(self.dataloader.dir, "html", "rules", n) if not os.path.exists(f): n = '' gi.rules_filename = n # cache the filename for next use return n def getGameSaveName(self, id): if os.path.supports_unicode_filenames: return self.getGameTitleName(id) n = self.gdb.get(id).en_name # english name if not n: return None return re.sub(r"[\s]", "_", latin1_normalize(n)) def _choice(self, lst): return self.miscrandom.choice(lst) def chooseRandomOutOfGames(self, games): return self._choice(games) def getRandomGameId(self): return self._choice(self.gdb.getGamesIdSortedById()) def getAllUserNames(self): names = [] for n in self.stats.games_stats.keys(): if n is None: # demo continue if self.stats.games_stats[n]: names.append(n) if self.opt.player not in names: names.append(self.opt.player) names.sort() return names def getGamesForSolver(self): return self.gdb.getGamesForSolver() # # plugins # def loadPlugins(self, dirname): for name in self._my_list_dir(dirname): m = re.search(r"^(.+)\.py$", name) n = os.path.join(dirname, name) if m and os.path.isfile(n): try: loadGame(m.group(1), n) except Exception as ex: if DEBUG: traceback.print_exc() print_err(_("error loading plugin %(file)s: %(err)s") % {'file': n, 'err': ex}) # # init cardsets # # read & parse a cardset config.txt file - see class Cardset in resource.py def _readCardsetConfig(self, dirname, filename): cs = read_cardset_config(dirname, filename) # set offsets from options.cfg if cs.ident in self.opt.offsets: cs.CARD_XOFFSET, cs.CARD_YOFFSET = self.opt.offsets[cs.ident] return cs def initCardsets(self): """Load all valid cardset config.txt files and ignore invalid ones. """ screendepth = self.top.winfo_screendepth() manager = self.cardset_manager # find all available cardsets dirs = manager.getSearchDirs(self, ("cardsets", ""), "PYSOL_CARDSETS") if DEBUG: dirs += manager.getSearchDirs(self, "cardsets-*") found = [] found_names = [] # (to check for duplicates) for dirname in dirs: try: subdirs = [os.path.join(dirname, subdir) for subdir in os.listdir(dirname) if subdir.startswith('cardset-')] except EnvironmentError: traceback.print_exc() continue subdirs.sort() for d in subdirs: config_txt_path = os.path.join(d, "config.txt") if not os.path.isfile(config_txt_path): continue try: cs = self._readCardsetConfig(d, config_txt_path) except Exception: traceback.print_exc() cs = None if not cs: print_err('failed to parse cardset file: %s' % config_txt_path) continue back = cs.backnames[cs.backindex] back_im_path = os.path.join(d, back) shade_im_path = os.path.join(d, "shade" + cs.ext) if (cs.name not in found_names and cs.ext in IMAGE_EXTENSIONS and cs.CARDD <= screendepth and os.path.isfile(back_im_path) and os.path.isfile(shade_im_path)): found.append(cs) found_names.append(cs.name) # register cardsets for obj in found: if not manager.getByName(obj.name): manager.register(obj) # print obj.index, obj.name # # init tiles # def _init_tiles_process_dir(self, dirname, found, t): """docstring for _init_tiles_process_die""" names = [] if dirname and os.path.isdir(dirname): names = os.listdir(dirname) for name in names: if not name or not image_ext_re.search(name): continue f = os.path.join(dirname, name) if not os.path.isfile(f): continue tile = Tile() tile.filename = f n = image_ext_re.sub("", name) subdir = os.path.split(dirname)[-1] if subdir == 'stretch' or subdir == 'stretch-4k': tile.stretch = 1 if subdir == 'save-aspect' or subdir == 'save-aspect-4k': tile.stretch = 1 tile.save_aspect = 1 # n = re.sub("[-_]", " ", n) n = n.replace('_', ' ') tile.name = n key = n.lower() if key not in t: t.add(key) found.append((n, tile)) def initTiles(self): manager = self.tabletile_manager # find all available tiles dirs = manager.getSearchDirs( self, ("tiles-*", os.path.join("tiles", "stretch"), os.path.join("tiles", "stretch-4k"), os.path.join("tiles", "save-aspect"), os.path.join("tiles", "save-aspect-4k")), "PYSOL_TILES") # print dirs found, t = [], set() for dirname in dirs: try: self._init_tiles_process_dir(dirname, found, t) except EnvironmentError: pass # register tiles found.sort() for f in found: obj = f[1] if not manager.getByName(obj.name): manager.register(obj) def _my_list_dir(self, dirname): """docstring for _my_list_dir""" if dirname and os.path.isdir(dirname): names = os.listdir(dirname) names = list(map(os.path.normcase, names)) names.sort() return names else: return [] # # init samples / music # def initResource(self, manager, dirs, ext_re, Resource_Class): found, t = [], set() for dirname in dirs: dirname = dirname.strip() if dirname: dirname = os.path.normpath(dirname) try: for name in self._my_list_dir(dirname): if not name or not ext_re.search(name): continue f = os.path.join(dirname, name) f = os.path.normpath(f) if not os.path.isfile(f): continue obj = Resource_Class() obj.filename = f n = ext_re.sub("", name.strip()) obj.name = n key = n.lower() if key not in t: t.add(key) found.append((n, obj)) except EnvironmentError: pass # register songs found.sort() if manager: for f in found: obj = f[1] if not manager.getByName(obj.name): manager.register(obj) return found def initSamples(self): manager = self.sample_manager # find all available samples dirs = manager.getSearchDirs( self, ("sound", os.path.join("sound", "extra"))) # print dirs ext_re = re.compile(r"\.((wav))$", re.I) self.initResource(manager, dirs, ext_re, Sample) def initMusic(self): music_manager = self.music_manager # find all available music songs dirs = music_manager.getSearchDirs(self, "music-*", "PYSOL_MUSIC") # print('dirs =', dirs) ext_re = re.compile(self.audio.EXTENSIONS) self.initResource(music_manager, dirs, ext_re, Music)