1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00
PySolFC/pysollib/tk/selectgame.py

620 lines
25 KiB
Python

#!/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 <http://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------------
import os
from pysollib.gamedb import GI
from pysollib.mfxutil import KwStruct, Struct, destruct
from pysollib.mfxutil import format_time
from pysollib.mygettext import _
from pysollib.resource import CSI
from pysollib.ui.tktile.selecttree import SelectDialogTreeData
from pysollib.ui.tktile.tkutil import unbind_destroy
from six.moves import UserList
from six.moves import tkinter
from .selecttree import SelectDialogTreeCanvas
from .selecttree import SelectDialogTreeLeaf, SelectDialogTreeNode
from .tkwidget import MfxDialog, MfxScrolledCanvas
# ************************************************************************
# * Nodes
# ************************************************************************
class SelectGameLeaf(SelectDialogTreeLeaf):
pass
class SelectGameNode(SelectDialogTreeNode):
def _getContents(self):
contents = []
if isinstance(self.select_func, UserList):
# key/value pairs
for id, name in self.select_func:
if id and name:
node = SelectGameLeaf(self.tree, self, name, key=id)
contents.append(node)
else:
for gi in self.tree.data.all_games_gi:
if gi and self.select_func is None:
# All games
# name = '%s (%s)' % (gi.name, CSI.TYPE_NAME[gi.category])
name = gi.name
node = SelectGameLeaf(self.tree, self, name, key=gi.id)
contents.append(node)
elif gi and self.select_func(gi):
name = gi.name
node = SelectGameLeaf(self.tree, self, name, key=gi.id)
contents.append(node)
return contents or self.tree.data.no_games
# ************************************************************************
# * Tree database
# ************************************************************************
class SelectGameData(SelectDialogTreeData):
def __init__(self, app):
SelectDialogTreeData.__init__(self)
self.all_games_gi = list(map(
app.gdb.get,
app.gdb.getGamesIdSortedByName()))
self.no_games = [SelectGameLeaf(None, None, _("(no games)"), None), ]
#
s_by_type = s_oriental = s_special = s_original = s_contrib = \
s_mahjongg = None
g = []
for data in (GI.SELECT_GAME_BY_TYPE,
GI.SELECT_ORIENTAL_GAME_BY_TYPE,
GI.SELECT_SPECIAL_GAME_BY_TYPE,
GI.SELECT_ORIGINAL_GAME_BY_TYPE,
GI.SELECT_CONTRIB_GAME_BY_TYPE,
):
gg = []
for name, select_func in data:
if name is None or not list(filter(
select_func, self.all_games_gi)):
continue
gg.append(SelectGameNode(None, _(name), select_func))
g.append(gg)
def select_mahjongg_game(gi):
return gi.si.game_type == GI.GT_MAHJONGG
gg = None
if list(filter(select_mahjongg_game, self.all_games_gi)):
gg = SelectGameNode(None, _("Mahjongg Games"),
select_mahjongg_game)
g.append(gg)
if g[0]:
s_by_type = SelectGameNode(None, _("French Games"),
tuple(g[0]), expanded=1)
if g[1]:
s_oriental = SelectGameNode(None, _("Oriental Games"),
tuple(g[1]))
if g[2]:
s_special = SelectGameNode(None, _("Special Games"),
tuple(g[2]))
if g[3]:
s_original = SelectGameNode(None, _("Original Games"),
tuple(g[3]))
# if g[4]:
# s_contrib = SelectGameNode(None, "Contributed Games", tuple(g[4]))
if g[5]:
s_mahjongg = g[5]
#
s_by_compatibility, gg = None, []
for name, games in GI.GAMES_BY_COMPATIBILITY:
def select_func(gi, games=games):
return gi.id in games
if name is None or not list(filter(
select_func, self.all_games_gi)):
continue
gg.append(SelectGameNode(None, name, select_func))
if 1 and gg:
s_by_compatibility = SelectGameNode(None, _("by Compatibility"),
tuple(gg))
#
s_by_pysol_version, gg = None, []
for name, games in GI.GAMES_BY_PYSOL_VERSION:
def select_func(gi, games=games):
return gi.id in games
if name is None or not list(filter(
select_func, self.all_games_gi)):
continue
name = _("New games in v. %(version)s") % {'version': name}
gg.append(SelectGameNode(None, name, select_func))
if 1 and gg:
s_by_pysol_version = SelectGameNode(None, _("by PySol version"),
tuple(gg))
s_by_inventors, gg = None, []
for name, games in GI.GAMES_BY_INVENTORS:
def select_func(gi, games=games):
return gi.id in games
if name is None or not list(filter(
select_func, self.all_games_gi)):
continue
gg.append(SelectGameNode(None, name, select_func))
if 1 and gg:
s_by_inventors = SelectGameNode(None, _("by Inventors"),
tuple(gg))
#
ul_alternate_names = UserList(
list(app.gdb.getGamesTuplesSortedByAlternateName()))
#
self.rootnodes = [_f for _f in (
SelectGameNode(None, _("All Games"), None, expanded=0),
SelectGameNode(None, _("Alternate Names"), ul_alternate_names),
SelectGameNode(None, _("Popular Games"),
lambda gi: gi.si.game_flags & GI.GT_POPULAR),
s_by_type,
s_mahjongg,
s_oriental,
s_special,
SelectGameNode(None, _("Custom Games"),
lambda gi: gi.si.game_type == GI.GT_CUSTOM),
SelectGameNode(None, _('by Skill Level'), (
SelectGameNode(None, _('Luck only'),
lambda gi: gi.skill_level == GI.SL_LUCK),
SelectGameNode(None, _('Mostly luck'),
lambda gi: gi.skill_level == GI.SL_MOSTLY_LUCK),
SelectGameNode(None, _('Balanced'),
lambda gi: gi.skill_level == GI.SL_BALANCED),
SelectGameNode(
None, _('Mostly skill'),
lambda gi: gi.skill_level == GI.SL_MOSTLY_SKILL),
SelectGameNode(None, _('Skill only'),
lambda gi: gi.skill_level == GI.SL_SKILL),
)),
SelectGameNode(None, _("by Game Feature"), (
SelectGameNode(None, _("by Number of Cards"), (
SelectGameNode(None, _("32 cards"),
lambda gi: gi.si.ncards == 32),
SelectGameNode(None, _("48 cards"),
lambda gi: gi.si.ncards == 48),
SelectGameNode(None, _("52 cards"),
lambda gi: gi.si.ncards == 52),
SelectGameNode(None, _("64 cards"),
lambda gi: gi.si.ncards == 64),
SelectGameNode(None, _("78 cards"),
lambda gi: gi.si.ncards == 78),
SelectGameNode(None, _("104 cards"),
lambda gi: gi.si.ncards == 104),
SelectGameNode(None, _("144 cards"),
lambda gi: gi.si.ncards == 144),
SelectGameNode(
None, _("Other number"),
lambda gi: gi.si.ncards not in (32, 48, 52,
64, 78, 104, 144)),
)),
SelectGameNode(None, _("by Number of Decks"), (
SelectGameNode(None, _("1 deck games"),
lambda gi: gi.si.decks == 1),
SelectGameNode(None, _("2 deck games"),
lambda gi: gi.si.decks == 2),
SelectGameNode(None, _("3 deck games"),
lambda gi: gi.si.decks == 3),
SelectGameNode(None, _("4 deck games"),
lambda gi: gi.si.decks == 4),
)),
SelectGameNode(None, _("by Number of Redeals"), (
SelectGameNode(None, _("No redeal"),
lambda gi: gi.si.redeals == 0),
SelectGameNode(None, _("1 redeal"),
lambda gi: gi.si.redeals == 1),
SelectGameNode(None, _("2 redeals"),
lambda gi: gi.si.redeals == 2),
SelectGameNode(None, _("3 redeals"),
lambda gi: gi.si.redeals == 3),
SelectGameNode(None, _("Unlimited redeals"),
lambda gi: gi.si.redeals == -1),
SelectGameNode(None, _("Variable redeals"),
lambda gi: gi.si.redeals == -2),
SelectGameNode(
None, _("Other number of redeals"),
lambda gi: gi.si.redeals not in (-2, -1, 0, 1, 2, 3)),
)),
s_by_compatibility,
)),
s_by_pysol_version,
s_by_inventors,
SelectGameNode(None, _("Other Categories"), (
SelectGameNode(None, _("Games for Children (very easy)"),
lambda gi: gi.si.game_flags & GI.GT_CHILDREN),
SelectGameNode(None, _("Games with Scoring"),
lambda gi: gi.si.game_flags & GI.GT_SCORE),
SelectGameNode(None, _("Games with Stripped Decks"),
lambda gi: gi.si.game_flags & GI.GT_STRIPPED),
SelectGameNode(
None, _("Games with Separate Decks"),
lambda gi: gi.si.game_flags & GI.GT_SEPARATE_DECKS),
SelectGameNode(None, _("Open Games (all cards visible)"),
lambda gi: gi.si.game_flags & GI.GT_OPEN),
SelectGameNode(None, _("Relaxed Variants"),
lambda gi: gi.si.game_flags & GI.GT_RELAXED),
)),
s_original,
s_contrib,
) if _f]
# ************************************************************************
# * Canvas that shows the tree
# ************************************************************************
class SelectGameTreeWithPreview(SelectDialogTreeCanvas):
data = None
class SelectGameTree(SelectGameTreeWithPreview):
def singleClick(self, event=None):
self.doubleClick(event)
# ************************************************************************
# * Dialog
# ************************************************************************
class SelectGameDialog(MfxDialog):
Tree_Class = SelectGameTree
TreeDataHolder_Class = SelectGameTreeWithPreview
TreeData_Class = SelectGameData
def __init__(self, parent, title, app, gameid, **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)
#
self.app = app
self.gameid = gameid
self.random = None
if self.TreeDataHolder_Class.data is None:
self.TreeDataHolder_Class.data = self.TreeData_Class(app)
#
self.top.wm_minsize(200, 200)
font = app.getFont("default")
self.tree = self.Tree_Class(self, top_frame, key=gameid,
font=font, default=kw.default)
self.tree.frame.pack(fill='both', expand=True,
padx=kw.padx, pady=kw.pady)
#
focus = self.createButtons(bottom_frame, kw)
focus = self.tree.frame
self.mainloop(focus, kw.timeout)
def initKw(self, kw):
kw = KwStruct(kw,
strings=(None, None, _("&Cancel"),), default=0,
resizable=True,
separator=True,
padx=10, pady=10,
buttonpadx=10, buttonpady=5,
)
return MfxDialog.initKw(self, kw)
def destroy(self):
self.app = None
self.tree.updateNodesWithTree(self.tree.rootnodes, None)
self.tree.destroy()
MfxDialog.destroy(self)
def mDone(self, button):
if button == 0: # Ok or double click
self.gameid = self.tree.selection_key
self.tree.n_expansions = 1 # save xyview in any case
if button == 1: # Rules
doc = self.app.getGameRulesFilename(self.tree.selection_key)
if not doc:
return
dir = os.path.join("html", "rules")
from pysollib.help import help_html
help_html(self.app, doc, dir, self.top)
return
MfxDialog.mDone(self, button)
# ************************************************************************
# * Dialog
# ************************************************************************
class SelectGameDialogWithPreview(SelectGameDialog):
Tree_Class = SelectGameTreeWithPreview
def __init__(self, parent, title, app, gameid, bookmark=None, **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)
#
self.app = app
self.gameid = gameid
self.bookmark = bookmark
self.random = None
if self.TreeDataHolder_Class.data is None:
self.TreeDataHolder_Class.data = self.TreeData_Class(app)
#
self.top.wm_minsize(400, 200)
sw = self.top.winfo_screenwidth()
if sw >= 1100:
w1, w2 = 250, 600
elif sw >= 900:
w1, w2 = 250, 500
elif sw >= 800:
w1, w2 = 220, 480
else:
w1, w2 = 200, 300
# print sw, w1, w2
w2 = max(200, min(w2, 10 + 12*(app.subsampled_images.CARDW+10)))
# print sw, w1, w2
# padx, pady = kw.padx, kw.pady
padx, pady = kw.padx//2, kw.pady//2
# PanedWindow
paned_window = tkinter.PanedWindow(top_frame)
paned_window.pack(expand=True, fill='both')
left_frame = tkinter.Frame(paned_window)
right_frame = tkinter.Frame(paned_window)
paned_window.add(left_frame)
paned_window.add(right_frame)
# Tree
font = app.getFont("default")
self.tree = self.Tree_Class(self, left_frame, key=gameid,
default=kw.default, font=font, width=w1)
self.tree.frame.pack(padx=padx, pady=pady, expand=True, fill='both')
# LabelFrame
info_frame = tkinter.LabelFrame(right_frame, text=_('About game'))
stats_frame = tkinter.LabelFrame(right_frame, text=_('Statistics'))
info_frame.grid(row=0, column=0, padx=padx, pady=pady,
ipadx=padx, ipady=pady, sticky='nws')
stats_frame.grid(row=0, column=1, padx=padx, pady=pady,
ipadx=padx, ipady=pady, sticky='nws')
# Info
self.info_labels = {}
for n, t, f, row in (
('name', _('Name:'), info_frame, 0),
('altnames', _('Alternate names:'), info_frame, 1),
('category', _('Category:'), info_frame, 2),
('type', _('Type:'), info_frame, 3),
('skill_level', _('Skill level:'), info_frame, 4),
('decks', _('Decks:'), info_frame, 5),
('redeals', _('Redeals:'), info_frame, 6),
#
('played', _('Played:'), stats_frame, 0),
('won', _('Won:'), stats_frame, 1),
('lost', _('Lost:'), stats_frame, 2),
('time', _('Playing time:'), stats_frame, 3),
('moves', _('Moves:'), stats_frame, 4),
('percent', _('% won:'), stats_frame, 5),
):
title_label = tkinter.Label(f, text=t, justify='left', anchor='w')
title_label.grid(row=row, column=0, sticky='nw')
text_label = tkinter.Label(f, justify='left', anchor='w')
text_label.grid(row=row, column=1, sticky='nw')
self.info_labels[n] = (title_label, text_label)
# info_frame.columnconfigure(1, weight=1)
info_frame.rowconfigure(6, weight=1)
stats_frame.rowconfigure(6, weight=1)
# Canvas
self.preview = MfxScrolledCanvas(right_frame, width=w2)
self.preview.setTile(app, app.tabletile_index, force=True)
self.preview.grid(row=1, column=0, columnspan=3,
padx=padx, pady=pady, sticky='nsew')
right_frame.columnconfigure(1, weight=1)
right_frame.rowconfigure(1, weight=1)
#
focus = self.createButtons(bottom_frame, kw)
# set the scale factor
self.preview.canvas.preview = 2
# create a preview of the current game
self.preview_key = -1
self.preview_game = None
self.preview_app = None
self.updatePreview(gameid, animations=0)
# focus = self.tree.frame
self.mainloop(focus, kw.timeout)
def initKw(self, kw):
kw = KwStruct(kw,
strings=((_("&Rules"), 10), 'sep',
_("&Select"), _("&Cancel"),),
default=0,
)
return SelectGameDialog.initKw(self, kw)
def destroy(self):
self.deletePreview(destroy=1)
self.preview.unbind_all()
SelectGameDialog.destroy(self)
def deletePreview(self, destroy=0):
self.preview_key = -1
# clean up the canvas
if self.preview:
unbind_destroy(self.preview.canvas)
self.preview.canvas.deleteAllItems()
if destroy:
self.preview.canvas.delete("all")
#
# for l in self.info_labels.values():
# l.config(text='')
# destruct the game
if self.preview_game:
self.preview_game.endGame()
self.preview_game.destruct()
destruct(self.preview_game)
self.preview_game = None
# destruct the app
if destroy:
if self.preview_app:
destruct(self.preview_app)
self.preview_app = None
def updatePreview(self, gameid, animations=10):
if gameid == self.preview_key:
return
self.deletePreview()
canvas = self.preview.canvas
#
gi = self.app.gdb.get(gameid)
if not gi:
self.preview_key = -1
return
#
if self.preview_app is None:
self.preview_app = Struct(
# variables
audio=self.app.audio,
canvas=canvas,
cardset=self.app.cardset.copy(),
gamerandom=self.app.gamerandom,
gdb=self.app.gdb,
gimages=self.app.gimages,
images=None,
menubar=None,
miscrandom=self.app.miscrandom,
opt=self.app.opt.copy(),
startup_opt=self.app.startup_opt,
stats=self.app.stats.new(),
top=None,
top_cursor=self.app.top_cursor,
toolbar=None,
# methods
constructGame=self.app.constructGame,
getFont=self.app.getFont,
)
self.preview_app.opt.shadow = 0
self.preview_app.opt.shade = 0
#
c = self.app.cardsets_cache.get(gi.category)
c2 = None
if c:
c2 = c.get(gi.subcategory)
if not c2:
cardset = self.app.cardset_manager.getByName(
self.app.opt.cardset[gi.category][gi.subcategory][0])
self.app.loadCardset(cardset, id=gi.category,
tocache=True, noprogress=True)
c = self.app.cardsets_cache.get(gi.category)
if c:
c2 = c.get(gi.subcategory)
if c2:
self.preview_app.images = c2[2]
else:
self.preview_app.images = self.app.subsampled_images
self.preview_app.audio = None # turn off audio for initial dealing
if animations >= 0:
self.preview_app.opt.animations = animations
#
if self.preview_game:
self.preview_game.endGame()
self.preview_game.destruct()
# self.top.wm_title("Select Game - " +
# self.app.getGameTitleName(gameid))
title = self.app.getGameTitleName(gameid)
self.top.wm_title(_("Select Game - %(game)s") % {'game': title})
#
self.preview_game = gi.gameclass(gi)
self.preview_game.createPreview(self.preview_app)
#
random = None
if gameid == self.gameid:
random = self.app.game.random.copy()
if gameid == self.gameid and self.bookmark:
self.preview_game.restoreGameFromBookmark(self.bookmark)
else:
self.preview_game.newGame(random=random, autoplay=1)
gw, gh = self.preview_game.width, self.preview_game.height
canvas.config(scrollregion=(0, 0, gw, gh))
canvas.xview_moveto(0)
canvas.yview_moveto(0)
#
self.preview_app.audio = self.app.audio
if self.app.opt.animations:
self.preview_app.opt.animations = 10
else:
self.preview_app.opt.animations = 0
# save seed
self.random = self.preview_game.random.copy()
self.random.origin = self.random.ORIGIN_PREVIEW
self.preview_key = gameid
#
self.updateInfo(gameid)
#
rules_button = self.buttons[0]
if self.app.getGameRulesFilename(gameid):
rules_button.config(state="normal")
else:
rules_button.config(state="disabled")
def updateInfo(self, gameid):
gi = self.app.gdb.get(gameid)
# info
name = gi.name
altnames = '\n'.join(gi.altnames)
category = _(CSI.TYPE[gi.category])
type = ''
if gi.si.game_type in GI.TYPE_NAMES:
type = _(GI.TYPE_NAMES[gi.si.game_type])
skill_level = GI.SKILL_LEVELS.get(gi.skill_level)
if gi.redeals == -2:
redeals = _('variable')
elif gi.redeals == -1:
redeals = _('unlimited')
else:
redeals = str(gi.redeals)
# stats
won, lost, time, moves = self.app.stats.getFullStats(
self.app.opt.player, gameid)
if won+lost > 0:
percent = "%.1f" % (100.0*won/(won+lost))
else:
percent = "0.0"
time = format_time(time)
moves = str(round(moves, 1))
for n, t in (
('name', name),
('altnames', altnames),
('category', category),
('type', type),
('skill_level', skill_level),
('decks', gi.decks),
('redeals', redeals),
('played', won+lost),
('won', won),
('lost', lost),
('time', time),
('moves', moves),
('percent', percent),
):
title_label, text_label = self.info_labels[n]
if t in ('', None):
title_label.grid_remove()
text_label.grid_remove()
else:
title_label.grid()
text_label.grid()
text_label.config(text=t)