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 DEFAULT_BUTTON_ICON_STYLE
from pysollib.options import DEFAULT_DEMO_LOGO_STYLE
from pysollib.options import DEFAULT_DIALOG_ICON_STYLE
from pysollib.options import DEFAULT_PAUSE_TEXT_STYLE
from pysollib.options import DEFAULT_REDEAL_ICON_STYLE
from pysollib.options import DEFAULT_TOOLBAR_STYLE
from pysollib.options import DEFAULT_TREE_ICON_STYLE
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
from pysollib.settings import TOOLKIT
from pysollib.speech import Speech
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
_DARK_LUMINANCE = 0.4
def resolve_toolbar_images_dir(images_root, style, size, default_style,
use_light):
"""Pick toolbar icon directory.
``images_root`` is the ``images`` directory. A set that contains
``light/<size>`` uses that tree for dark themes, when ``use_light``
is true. Any other name is left alone. A name with a directory
(or size) missing is replaced with ``default_style``.
"""
def variant(name):
base = os.path.join(images_root, 'toolbar', name)
if not os.path.isdir(base):
return None
if use_light:
light = os.path.join(base, 'light', size)
if os.path.isdir(light):
return light
normal = os.path.join(base, size)
if os.path.isdir(normal):
return normal
return None
found = variant(style)
if found is not None:
return style, found
if style != default_style:
found = variant(default_style)
if found is not None:
return default_style, found
return default_style, os.path.join(
images_root, 'toolbar', default_style, size)
def resolve_image_style(data_dir, category, style, default_style):
"""Return the style name to load for an other-images category.
``none`` means no images. Any other name whose directory is missing is
replaced with ``default_style``.
"""
def present(name):
if name == 'none':
return True
return os.path.isdir(
os.path.join(data_dir, 'images', category, name))
if present(style):
return style
return default_style
def interface_is_dark(root):
"""True when the UI theme background is dark.
Tile themes are sampled from the current ttk background. Kivy uses the
main-window chrome color. A color that cannot be read counts as light.
"""
if TOOLKIT == 'kivy':
from pysollib.kivy.LApp import LColorToLuminance, MAIN_WINDOW_BG
return LColorToLuminance(MAIN_WINDOW_BG) < _DARK_LUMINANCE
if root is None:
return False
color = ''
from pysollib.settings import USE_TILE
if TOOLKIT == 'tk' and USE_TILE:
try:
import tkinter.ttk as ttk
style = ttk.Style(root)
color = (style.lookup('TFrame', 'background')
or style.lookup('.', 'background')
or '')
except Exception:
color = ''
if not color:
try:
color = root.cget('background')
except Exception:
return False
try:
red, green, blue = (c / 65535.0 for c in root.winfo_rgb(color))
except Exception:
return False
luminance = 0.2126 * red + 0.7152 * green + 0.0722 * blue
return luminance < _DARK_LUMINANCE
_GameStatResult = GameStatResult
GameStat = pysollib.app_stat.GameStat
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.speech = Speech()
self.startup_opt = self.opt.copy()
self.stats = Statistics()
self.splashscreen = 1
self.top = None
self.top_bg = None
self.top_cursor = None
self.menubar = None
self.toolbar = None
self.canvas = None
self.scrolled_canvas = None
self.statusbar = None
self.game = None
self.dataloader = None
self.audio = None
self.images = None
self.subsampled_images = None
self.gimages = Struct(
demo=[],
pause=[],
logos=[],
redeal=[],
)
self.progress_images = []
self.cardset_manager = CardsetManager()
self.cardset = None
self.cardsets_cache = {}
self.tabletile_manager = TileManager()
self.tabletile_index = 0
self.sample_manager = SampleManager()
self.music_manager = MusicManager()
self.music_playlist = []
self.intro = Struct(
progress=None,
)
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"),
)
for k, v in self.dn.__dict__.items():
v = os.path.normpath(v)
self.dn.__dict__[k] = v
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
self.gamerandom = PysolRandom()
self.miscrandom = PysolRandom()
player = getusername()
if not player:
player = "unknown"
player = player[:30]
self.opt.player = player
self.nextgame = Struct(
id=0,
random=None,
loadedgame=None,
startdemo=0,
cardset=None,
holdgame=0,
bookmark=None,
)
self.commandline = Struct(
loadgame=None,
game=None,
gameid=None,
deal=None,
)
self.demo_counter = 0
def mainloop(self):
try:
approc = self.mainproc()
approc.send(None)
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 %s,%s', 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:
game.destruct()
destruct(game)
def _main_loop(self):
"""docstring for _main_loop"""
try:
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:
if id_ == 2:
raise
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()
self.wm_save_state()
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
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()
finally:
self.wm_withdraw()
destroy_help_html()
destroy_find_card_dialog()
destroy_full_picture_dialog()
destroy_solver_dialog()
self.opt.last_gameid = id_
try:
self.saveOptions()
except Exception:
traceback.print_exc()
try:
self.saveStatistics()
except Exception:
traceback.print_exc()
try:
self.audio.destroy()
except Exception:
traceback.print_exc()
if TOOLKIT == 'kivy':
self.top.quit()
while True:
logging.info('App: mainloop end position')
yield
def mainproc(self):
self.startup_opt = self.opt.copy()
try:
self.loadStatistics()
except Exception:
traceback.print_exc()
if 0 < self.opt.game_holded != self.opt.last_gameid:
self.opt.last_gameid = self.opt.game_holded
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)
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)
if self.intro.progress:
self.intro.progress.update(step=1)
self.menubar = PysolMenubar(self, self.top,
progress=self.intro.progress)
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)
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)
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)
g = self.getGameClass(id_)
if g is None:
id_ = 2
random = None
g = self.getGameClass(id_)
if g is None:
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
self.game.create(self)
self.menubar.connectGame(self.game)
if self.toolbar:
self.toolbar.connectGame(self.game)
self.game.updateStatus(player=self.opt.player)
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()
self.toolbar.config(
'shuffle',
self.opt.toolbar_vars['shuffle'] and self.game.canShuffle())
if self.intro.progress:
self.intro.progress.destroy()
destruct(self.intro.progress)
self.intro.progress = None
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
if self.opt.splashscreen and self.splashscreen > 0:
status = help_about(self, timeout=20000, sound=0)
if status == 2:
if autoplay:
self.nextgame.startdemo = 1
self.splashscreen = 0
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
self.game.busy = 0
self.top.mainloop()
def freeGame(self):
self.menubar.connectGame(None)
self.toolbar.connectGame(None)
self.canvas.deleteAllItems()
self.canvas.update_idletasks()
if self.game:
self.game.destruct()
destruct(self.game)
self.game = None
self.top.connectApp(None)
def wm_save_state(self):
if self.top:
s = self.top.wm_state()
if s == "zoomed":
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)
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):
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))
style = self._selectImageStyle(
'dialog_icon_style', 'dialog', DEFAULT_DIALOG_ICON_STYLE)
dirname = os.path.join('images', 'dialog', style)
for f in ('error', 'info', 'question', 'warning'):
fn = self.dataloader.findImage(f, dirname)
im = loadImage(fn)
MfxMessageDialog.img[f] = im
MfxDialog.button_img = {}
style = self._selectImageStyle(
'button_icon_style', 'buttons', DEFAULT_BUTTON_ICON_STYLE)
if TOOLKIT == 'tk' and style != 'none':
dirname = os.path.join('images', 'buttons', style)
for f, labels in (
('ok', (_('&OK'), _('&Select'), _('&Nice'), _('&Enjoy'),
_('&Great'), _('&Cool'), _('&Yeah'), _('&Wow'),
_('&Oh well'), _("&That's life"), _('&Hmm'))),
('cancel', (_('&Cancel'), _('&Close'), _('&Quit'))),
('apply', (_('&Apply'), _('&Start'))),
('new', (_('&New'), _('&New game'))),
('back', (_('&Back to game'),)),
('reset', (_('&Reset...'), _('&Restart'))),
('help', (_('&Rules'), _('&Info...'), _('&Credits'))),
('next', (_('&Next number'), _('&Play'),
_('&Play this game'))),
('clear', (_('C&lear'),)),
('color', (_('&Solid color...'),)),
('save', (_('&Save to file'),)),
('statistics', (_('&Statistics...'),)),
):
im = loadImage(self.dataloader.findImage(f, dirname))
for n in labels:
MfxDialog.button_img[n] = im
def loadImages2(self):
style = self._selectImageStyle(
'redeal_icon_style', 'redealicons', DEFAULT_REDEAL_ICON_STYLE)
dirname = os.path.join("images", "redealicons", style)
self.gimages.redeal = []
for f in ("stopsign", "redeal",):
self.gimages.redeal.append(self.dataloader.findImage(f, dirname))
style = self._selectImageStyle(
'demo_logo_style', 'demo', DEFAULT_DEMO_LOGO_STYLE)
dirname = os.path.join("images", "demo", 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
style = self._selectImageStyle(
'pause_text_style', 'pause', DEFAULT_PAUSE_TEXT_STYLE)
dirname = os.path.join("images", "pause", 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
def loadImages3(self):
SelectDialogTreeData.img = []
style = self._selectImageStyle(
'tree_icon_style', 'tree', DEFAULT_TREE_ICON_STYLE)
dirname = os.path.join('images', 'tree', style)
for f in ('folder', 'openfolder', 'node', 'emptynode'):
fn = self.dataloader.findImage(f, dirname)
im = loadImage(fn)
SelectDialogTreeData.img.append(im)
dirname = os.path.join('images', 'htmlviewer')
fn = self.dataloader.findImage('disk', dirname)
HTMLViewer.symbols_fn['disk'] = fn
def loadImages4(self):
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]
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 _toolbarIconSizeName(self):
if TOOLKIT == 'kivy':
return 'xlarge'
if self.opt.toolbar_size == 2:
return 'xlarge'
if self.opt.toolbar_size == 1:
return 'large'
return 'small'
def _syncImageStyleMenu(self, attr):
menubar = getattr(self, 'menubar', None)
if menubar is None:
return
var = getattr(getattr(menubar, 'tkopt', None), attr, None)
if var is None:
return
style = getattr(self.opt, attr)
if hasattr(var, 'value'):
var.value = style
elif hasattr(var, 'set'):
var.set(style)
def _selectImageStyle(self, attr, category, default_style):
style = resolve_image_style(
self.dataloader.dir, category, getattr(self.opt, attr),
default_style)
if style != getattr(self.opt, attr):
setattr(self.opt, attr, style)
self._syncImageStyleMenu(attr)
return style
def _syncToolbarStyleMenu(self):
self._syncImageStyleMenu('toolbar_style')
def getToolbarImagesDir(self):
size = self._toolbarIconSizeName()
images_root = os.path.join(self.dataloader.dir, 'images')
style, dirname = resolve_toolbar_images_dir(
images_root, self.opt.toolbar_style, size,
DEFAULT_TOOLBAR_STYLE, interface_is_dark(self.top))
if style != self.opt.toolbar_style:
self.opt.toolbar_style = style
self._syncToolbarStyleMenu()
return dirname
def refreshToolbarImages(self):
toolbar = getattr(self, 'toolbar', None)
if toolbar is None or not hasattr(toolbar, 'updateImages'):
return False
dirname = self.getToolbarImagesDir()
return bool(toolbar.updateImages(dirname, self.opt.toolbar_size))
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)
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
def updateCardset(self, id=0, update=7):
cs = self.images.cs
self.cardset = cs
self.nextgame.cardset = cs
self.cardset_manager.setSelected(cs.index)
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)
def loadCardset(self, cs, id=0, update=7, progress=None,
tocache=False, noprogress=False):
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
c = self.cardsets_cache.get(cs.type)
if c:
c2 = c.get(cs.subtype)
if c2 and c2[0] == cs.ident:
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)
c = self.cardsets_cache.get(cs.type)
if c:
c2 = c.get(cs.subtype)
if c2:
destruct(c2[1])
self.cardsets_cache[cs.type] = {}
self.cardsets_cache[cs.type][cs.subtype] = (cs.ident, images,
simages)
if not tocache:
if self.cardset:
if self.cardset.ident != cs.ident:
if self.cardset.type == cs.type:
self.opt.games_geometry = {}
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
self.nextgame.cardset = self.cardset
if self.cardset:
self.cardset_manager.setSelected(self.cardset.index)
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,
):
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:
t1 = t0
elif gc == GI.GC_MATCHING:
t0 = "Matching"
if cs.ncards < (gi.ncards / 2):
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:
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)
if cs:
if not t[1]:
return cs, 1, t
if trychange:
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
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):
subcategoryLabel = ''
if self.isSubcategoryRelevant(gi):
subcategoryLabel = ("(%s) ") % self.getSubcategoryName(gi)
cardCountLabel = ''
if self.isCardCountRelevant(gi):
cardCountLabel = _("(with %d or more cards)") % gi.ncards
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 %(subcategory)stype cardset.
%(cardcount)s
''') % {'cardset': cardset.name, 'game': gi.name,
'correct_type': t[0], 'subcategory': subcategoryLabel,
'cardcount': cardCountLabel},
strings=(_("&OK"),), default=0)
def isSubcategoryRelevant(self, gi):
if (gi.category == GI.GC_FRENCH):
return gi.subcategory == GI.GS_JOKER_DECK
if (gi.category == GI.GC_PUZZLE):
return True
return False
def getSubcategoryName(self, gi):
by_type = CSI.SUBTYPE_NAME.get(gi.category)
if not by_type:
return ""
return by_type.get(gi.subcategory, "")
def isCardCountRelevant(self, gi):
return (gi.category == GI.GC_TRUMP_ONLY or
gi.category == GI.GC_MATCHING)
def selectCardset(self, title, key, initial_tab=None):
wasPaused = False
if not self.game.pause:
self.game.doPause()
wasPaused = True
d = SelectCardsetDialogWithPreview(
self.top, title=title, app=self,
manager=self.cardset_manager, key=key,
initial_tab=initial_tab)
if self.game.pause:
if wasPaused:
self.game.doPause()
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.preview_scale,
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.preview_scale,
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
def loadOptions(self):
self.opt.setDefaults(self.top)
if os.path.exists(self.fn.opt):
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:
self.stats.__dict__.update(stats.__dict__)
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)
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
return sorted(
self.gdb.getGamesIdSortedByName(), key=_key, reverse=True
)
def getGamesIdSortedByWon(self, player=''):
if player == '':
player = self.opt.player
def _key(a):
wa, la, ta, ma = self.stats.getFullStats(player, a)
return wa
return sorted(
self.gdb.getGamesIdSortedByName(), key=_key, reverse=True
)
def getGamesIdSortedByLost(self, player=''):
if player == '':
player = self.opt.player
def _key(a):
wa, la, ta, ma = self.stats.getFullStats(player, a)
return la
return sorted(
self.gdb.getGamesIdSortedByName(), key=_key, reverse=True
)
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)
return sorted(
self.gdb.getGamesIdSortedByName(), key=_key, reverse=True
)
def getGamesIdSortedByPlayingTime(self, player=''):
if player == '':
player = self.opt.player
def _key(a):
wa, la, ta, ma = self.stats.getFullStats(player, a)
return ta
return sorted(
self.gdb.getGamesIdSortedByName(), key=_key, reverse=True
)
def getGamesIdSortedByMoves(self, player=''):
if player == '':
player = self.opt.player
def _key(a):
wa, la, ta, ma = self.stats.getFullStats(player, a)
return ma
return sorted(
self.gdb.getGamesIdSortedByName(), key=_key, reverse=True
)
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'
f = os.path.join(self.dataloader.dir, "html", "rules", n)
if not os.path.exists(f):
n = ''
gi.rules_filename = n
return n
def getGameSaveName(self, id):
if os.path.supports_unicode_filenames:
return self.getGameTitleName(id)
n = self.gdb.get(id).en_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:
if n is None:
continue
if self.stats.games_stats[n]:
names.append(n)
if self.opt.player not in names:
names.append(self.opt.player)
return sorted(names)
def getGamesForSolver(self):
return self.gdb.getGamesForSolver()
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})
def _readCardsetConfig(self, dirname, filename):
cs = read_cardset_config(dirname, filename)
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
dirs = manager.getSearchDirs(self, ("cardsets", ""), "PYSOL_CARDSETS")
if DEBUG:
dirs += manager.getSearchDirs(self, "cardsets-*")
found = []
found_names = []
for dirname in dirs:
try:
subdirs = sorted(os.path.join(dirname, subdir)
for subdir in os.listdir(dirname)
if subdir.startswith('cardset-'))
except EnvironmentError:
traceback.print_exc()
continue
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)
for obj in found:
if not manager.getByName(obj.name):
manager.register(obj)
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 = 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
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")
found, t = [], set()
for dirname in dirs:
try:
self._init_tiles_process_dir(dirname, found, t)
except EnvironmentError:
pass
for f in sorted(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)
return sorted(map(os.path.normcase, names))
return []
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
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
dirs = manager.getSearchDirs(
self, ("sound", os.path.join("sound", "extra")))
ext_re = re.compile(r"\.((wav))$", re.I)
self.initResource(manager, dirs, ext_re, Sample)
def initMusic(self):
music_manager = self.music_manager
dirs = music_manager.getSearchDirs(self, "music-*", "PYSOL_MUSIC")
ext_re = re.compile(self.audio.EXTENSIONS)
self.initResource(music_manager, dirs, ext_re, Music)