1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00
PySolFC/pysollib/resource.py
Alexandre Detiste 148f189a74
trim usage of six (#382)
This is artisanal manual craftwork :-)

     def mDone(self, button):
         if button == 0:        # "OK" or double click
-            if isinstance(self.tree.selection_key, six.string_types):
-                self.key = str(self.tree.selection_key)
-            else:
-                self.key = self.tree.selection_key
+            self.key = self.tree.selection_key
2024-09-18 20:33:10 -04:00

742 lines
23 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 glob
import os
import traceback
from pysollib.mfxutil import Image, KwStruct, Struct, USE_PIL
from pysollib.mygettext import _
from pysollib.settings import DEBUG
# ************************************************************************
# * Abstract
# ************************************************************************
class Resource(Struct):
def __init__(self, **kw):
kw = KwStruct(
kw,
name="",
filename="",
basename="", # basename of filename
absname="", # absolute filename
# implicit
index=-1,
error=0, # error while loading this resource
)
Struct.__init__(self, **kw.getKw())
def getSortKey(self):
return self.name.lower()
class ResourceManager:
def __init__(self):
self._selected_key = -1
self._objects = []
self._objects_by_name = None
self._objects_cache_name = {}
self._objects_cache_filename = {}
self._objects_cache_basename = {}
self._objects_cache_absname = {}
def getSelected(self):
return self._selected_key
def setSelected(self, index):
assert -1 <= index < len(self._objects)
self._selected_key = index
def len(self):
return len(self._objects)
def register(self, obj):
assert obj.index == -1
assert obj.name and obj.name not in self._objects_cache_name
self._objects_cache_name[obj.name] = obj
if obj.filename:
obj.absname = os.path.abspath(obj.filename)
obj.basename = os.path.basename(obj.filename)
self._objects_cache_filename[obj.filename] = obj
self._objects_cache_basename[obj.basename] = obj
self._objects_cache_absname[obj.absname] = obj
obj.index = len(self._objects)
self._objects.append(obj)
self._objects_by_name = None # invalidate
def get(self, index):
if 0 <= index < len(self._objects):
return self._objects[index]
return None
def getByName(self, key):
return self._objects_cache_name.get(key)
def getByBasename(self, key):
return self._objects_cache_basename.get(key)
def getAll(self):
return tuple(self._objects)
def getAllSortedByName(self):
if self._objects_by_name is None:
lst = [(obj.getSortKey(), obj) for obj in self._objects]
lst.sort()
self._objects_by_name = tuple([item[1] for item in lst])
return self._objects_by_name
#
# static methods
#
def _addDir(self, result, dir):
try:
if dir:
dir = os.path.normpath(dir)
if dir and os.path.isdir(dir) and dir not in result:
result.append(dir)
except EnvironmentError:
pass
def getSearchDirs(self, app, search, env=None):
"""Get a list of normalized directory paths. The returned list has no
duplicates."""
if isinstance(search, str):
search = (search,)
result = []
if env:
for d in os.environ.get(env, "").split(os.pathsep):
self._addDir(result, d.strip())
for dir in (app.dataloader.dir, app.dn.maint, app.dn.config):
if not dir:
continue
dir = os.path.normpath(dir)
if not dir or not os.path.isdir(dir):
continue
for s in search:
try:
if s[-2:] == "-*":
d = os.path.normpath(os.path.join(dir, s[:-2]))
self._addDir(result, d)
globdirs = glob.glob(d + "-*")
globdirs.sort()
for d in globdirs:
self._addDir(result, d)
else:
self._addDir(result, os.path.join(dir, s))
except EnvironmentError:
traceback.print_exc()
pass
if DEBUG >= 6:
print("getSearchDirs", env, search, "->", result)
return result
# ************************************************************************
# * Cardset
# ************************************************************************
# CardsetInfo constants
class CSI:
# cardset size
SIZE_TINY = 1
SIZE_SMALL = 2
SIZE_MEDIUM = 3
SIZE_LARGE = 4
SIZE_XLARGE = 5
SIZE_HIRES = 6
# cardset types
TYPE_FRENCH = 1
TYPE_HANAFUDA = 2
TYPE_TAROCK = 3
TYPE_MAHJONGG = 4
TYPE_HEXADECK = 5
TYPE_MUGHAL_GANJIFA = 6
TYPE_NAVAGRAHA_GANJIFA = 7
TYPE_DASHAVATARA_GANJIFA = 8
TYPE_TRUMP_ONLY = 9
TYPE_MATCHING = 10
TYPE_PUZZLE = 11
TYPE_ISHIDO = 12
# cardset subtypes
# (french)
SUBTYPE_NONE = 0
SUBTYPE_JOKER_DECK = 1
# (puzzle)
SUBTYPE_3X3 = 3
SUBTYPE_4X4 = 4
SUBTYPE_5X5 = 5
SUBTYPE_6X6 = 6
SUBTYPE_7X7 = 7
SUBTYPE_8X8 = 8
SUBTYPE_9X9 = 9
SUBTYPE_10X10 = 10
TYPE = {
1: _("French type (52-54 cards)"),
2: _("Hanafuda type (48 cards)"),
3: _("Tarock type (78 cards)"),
4: _("Mahjongg type (42 tiles)"),
5: _("Hex A Deck type (68 cards)"),
6: _("Mughal Ganjifa type (96 cards)"),
7: _("Navagraha Ganjifa type (108 cards)"),
8: _("Dashavatara Ganjifa type (120 cards)"),
9: _("Trumps only type (variable cards)"),
10: _("Matching type (variable cards)"),
11: _("Puzzle type (variable pieces)"),
12: _("Ishido type (36 tiles)")
}
TYPE_NAME = {
1: _("French"),
2: _("Hanafuda"),
3: _("Tarock"),
4: _("Mahjongg"),
5: _("Hex A Deck"),
6: _("Mughal Ganjifa"),
7: _("Navagraha Ganjifa"),
8: _("Dashavatara Ganjifa"),
9: _("Trumps only"),
10: _("Matching"),
11: _("Puzzle"),
12: _("Ishido")
}
SUBTYPE_NAME = {
1: {0: _("No Jokers"), 1: _("Joker Deck")},
11: {3: _("3x3"),
4: _("4x4"),
5: _("5x5"),
6: _("6x6"),
7: _("7x7"),
8: _("8x8"),
9: _("9x9"),
10: _("10x10")}
}
TYPE_ID = {
1: "french",
2: "hanafuda",
3: "tarock",
4: "mahjongg",
5: "hex-a-deck",
6: "mughal-ganjifa",
7: "navagraha-ganjifa",
8: "dashavatara-ganjifa",
9: "trumps-only",
10: "matching",
11: "puzzle",
12: "ishido"
}
TYPE_SUITS = {
1: "cshd",
2: "abcdefghijkl",
3: "cshd",
4: "abc",
5: "cshd",
6: "abcdefgh",
7: "abcdefghi",
8: "abcdefghij",
9: "",
10: "",
11: "",
12: "abcdef"
}
TYPE_RANKS = {
1: list(range(13)),
2: list(range(4)),
3: list(range(14)),
4: list(range(10)),
5: list(range(16)),
6: list(range(12)),
7: list(range(12)),
8: list(range(12)),
9: list(range(0)),
10: list(range(0)),
11: list(range(0)),
12: list(range(6))
}
TYPE_TRUMPS = {
1: (),
2: (),
3: list(range(22)),
4: list(range(12)),
5: list(range(4)),
6: (),
7: (),
8: (),
9: (),
10: (),
11: (),
12: ()
}
# cardset styles
STYLE = {
35: _("Abstract"), #
1: _("Adult"), #
2: _("Animals"), #
3: _("Anime"), #
4: _("Art"), #
5: _("Cartoons"), #
6: _("Children"), #
7: _("Classic Look"), #
8: _("Collectors"), # scanned collectors cardsets
9: _("Computers"), #
36: _("Divination"), # e.g. fortunetelling decks
10: _("Engines"), #
11: _("Fantasy"), #
37: _("Four Color"), #
30: _("Ganjifa"), #
12: _("Hanafuda"), #
29: _("Hex A Deck"), #
13: _("Holiday"), #
34: _("Ishido"), #
28: _("Mahjongg"), #
32: _("Matching"), #
38: _("Monochrome"), #
14: _("Movies"), #
31: _("Matrix"), #
15: _("Music"), #
16: _("Nature"), #
17: _("Operating Systems"), # e.g. cards with Linux logos
19: _("People"), # famous people
20: _("Places"), #
21: _("Plain"), #
22: _("Products"), #
33: _("Puzzle"), #
18: _("Round Cardsets"), #
23: _("Science Fiction"), #
24: _("Sports"), #
27: _("Tarock"), #
25: _("Vehicles"), #
26: _("Video Games"), #
}
# cardset nationality (suit and rank symbols)
NATIONALITY = {
1021: _("Australia"), #
1001: _("Austria"), #
1019: _("Belgium"), #
1010: _("Canada"), #
1011: _("China"), #
1012: _("Czech Republic"), #
1013: _("Denmark"), #
1003: _("England"), #
1004: _("France"), #
1006: _("Germany"), #
1014: _("Great Britain"), #
1015: _("Hungary"), #
1020: _("India"), #
1005: _("Italy"), #
1016: _("Japan"), #
1002: _("Netherlands"), #
1022: _("Portugal"), #
1007: _("Russia"), #
1008: _("Spain"), #
1017: _("Sweden"), #
1009: _("Switzerland"), #
1018: _("USA"), #
}
# cardset creation date
DATE = {
10: "1000 - 1099",
11: "1100 - 1199",
12: "1200 - 1299",
13: "1300 - 1399",
14: "1400 - 1499",
15: "1500 - 1599",
16: "1600 - 1699",
17: "1700 - 1799",
18: "1800 - 1899",
19: "1900 - 1999",
20: "2000 - 2099",
21: "2100 - 2199",
22: "2200 - 2299",
}
class CardsetConfig(Struct):
# see config.txt and _readCardsetConfig()
def __init__(self):
Struct.__init__(
self,
# line[0]
version=1,
ext=".gif",
type=CSI.TYPE_FRENCH,
ncards=-1,
styles=[],
year=0,
subtype=0,
mahjongg3d=False,
# line[1]
ident="",
name="",
# line[2]
CARDW=0,
CARDH=0,
CARDD=0,
# line[3]
CARD_XOFFSET=0,
CARD_YOFFSET=0,
SHADOW_XOFFSET=0,
SHADOW_YOFFSET=0,
# line[4]
backindex=0,
# line[5]
backnames=(),
# other
CARD_DX=0, # relative pos of real card image within Card
CARD_DY=0,
)
class Cardset(Resource):
def __init__(self, **kw):
# start with all fields from CardsetConfig
config = CardsetConfig()
kw = KwStruct(config.__dict__, **kw)
# si is the SelectionInfo struct that will be queried by
# the "select cardset" dialogs. It can be freely modified.
si = Struct(type=0, subtype=0, size=0, styles=[],
nationalities=[], dates=[])
kw = KwStruct(
kw,
# essentials
ranks=(),
suits=(),
trumps=(),
nbottoms=7,
nletters=4,
nshadows=1 + 13,
# selection criteria
si=si,
# implicit
backname=None,
dir="",
)
Resource.__init__(self, **kw.getKw())
def getFaceCardNames(self):
names = []
for suit in self.suits:
for rank in self.ranks:
names.append("%02d%s" % (rank + 1, suit))
for trump in self.trumps:
names.append("%02d%s" % (trump + 1, "z"))
assert len(names) == self.ncards
return names
def getPreviewCardNames(self):
names = self.getFaceCardNames()
pnames = []
ranks, suits = self.ranks, self.suits
lr, ls = len(ranks), len(suits)
if lr == 0 or ls == 0: # TYPE_TRUMP_ONLY
return names[:16], 4
if lr >= 4:
ls = min(ls, 4)
low_ranks, high_ranks = 1, 3
# if self.type == 3: high_ranks = 4
for rank in list(range(0, low_ranks)) + list(range(lr-high_ranks, lr)):
for suit in range(ls):
index = suit * len(self.ranks) + rank
pnames.append(names[index % len(names)])
return pnames, ls
def updateCardback(self, backname=None, backindex=None):
# update default back
if isinstance(backname, str):
if backname in self.backnames:
backindex = self.backnames.index(backname)
if isinstance(backindex, int):
self.backindex = backindex % len(self.backnames)
self.backname = self.backnames[self.backindex]
def saveSettings(self):
print('saveSettings')
class CardsetManager(ResourceManager):
def __init__(self):
ResourceManager.__init__(self)
self.registered_types = {}
self.registered_subtypes = {}
self.type_max_cards = {}
self.registered_sizes = {}
self.registered_styles = {}
self.registered_nationalities = {}
self.registered_dates = {}
self.uncategorized_styles = False
self.uncategorized_nationalities = False
self.uncategorized_dates = False
def _check(self, cs):
s = cs.type
if s not in CSI.TYPE:
return 0
cs.si.type = s
cs.si.subtype = cs.subtype
cs.suits = CSI.TYPE_SUITS[s]
cs.ranks = CSI.TYPE_RANKS[s]
cs.trumps = CSI.TYPE_TRUMPS[s]
if s == CSI.TYPE_FRENCH:
if cs.subtype == 1:
cs.trumps = list(range(2))
cs.nbottoms = 8
elif s == CSI.TYPE_HANAFUDA:
cs.nbottoms = 15
elif s == CSI.TYPE_TAROCK:
cs.nbottoms = 8
elif s == CSI.TYPE_MAHJONGG:
cs.nbottoms = 0
cs.nletters = 0
cs.nshadows = 0
elif s == CSI.TYPE_HEXADECK:
cs.nbottoms = 8
elif s == CSI.TYPE_MUGHAL_GANJIFA:
cs.nbottoms = 11
elif s == CSI.TYPE_NAVAGRAHA_GANJIFA:
# ???return 0 ## FIXME
cs.nbottoms = 12
elif s == CSI.TYPE_DASHAVATARA_GANJIFA:
cs.nbottoms = 13
elif s == CSI.TYPE_TRUMP_ONLY:
# ???return 0 ## FIXME
# cs.nbottoms = 7
# cs.ranks = ()
# cs.suits = ""
# cs.trumps = range(cs.ncards)
cs.nbottoms = 1
cs.nletters = 0
cs.nshadows = 0
cs.trumps = list(range(cs.ncards))
elif s == CSI.TYPE_MATCHING:
# ???return 0 ## FIXME
# cs.nbottoms = 7
# cs.ranks = ()
# cs.suits = ""
# cs.trumps = range(cs.ncards)
cs.nbottoms = 1
cs.nletters = 0
cs.nshadows = 0
cs.trumps = list(range(cs.ncards))
elif s == CSI.TYPE_PUZZLE:
# ???return 0 ## FIXME
# cs.nbottoms = 7
# cs.ranks = ()
# cs.suits = ""
# cs.trumps = range(cs.ncards)
cs.nbottoms = 1
cs.nletters = 0
cs.nshadows = 0
cs.trumps = list(range(cs.ncards))
elif s == CSI.TYPE_ISHIDO:
cs.nbottoms = 1
cs.nletters = 0
cs.nshadows = 0
else:
return 0
return 1
def register(self, cs):
if not self._check(cs):
return
cs.ncards = len(cs.ranks) * len(cs.suits) + len(cs.trumps)
cs.name = cs.name[:30]
if not (CSI.SIZE_TINY <= cs.si.size <= CSI.SIZE_HIRES):
CW, CH = cs.CARDW, cs.CARDH
if CW <= 55 and CH <= 72:
cs.si.size = CSI.SIZE_TINY
elif CW <= 60 and CH <= 85:
cs.si.size = CSI.SIZE_SMALL
elif CW <= 75 and CH <= 105:
cs.si.size = CSI.SIZE_MEDIUM
elif CW <= 90 and CH <= 125:
cs.si.size = CSI.SIZE_LARGE
elif CW <= 150 and CH <= 210:
cs.si.size = CSI.SIZE_XLARGE
else:
cs.si.size = CSI.SIZE_HIRES
#
keys = cs.styles[:]
cs.si.styles = tuple([s for s in keys if s in CSI.STYLE])
if len(cs.si.styles) == 0:
self.uncategorized_styles = True
for s in cs.si.styles:
self.registered_styles[s] = self.registered_styles.get(s, 0) + 1
cs.si.nationalities = tuple([s for s in keys if s in CSI.NATIONALITY])
if len(cs.si.nationalities) == 0:
self.uncategorized_nationalities = True
for s in cs.si.nationalities:
self.registered_nationalities[s] = \
self.registered_nationalities.get(s, 0) + 1
if cs.year == 0:
self.uncategorized_dates = True
keys = (cs.year // 100,)
cs.si.dates = tuple([s for s in keys if s in CSI.DATE])
for s in cs.si.dates:
self.registered_dates[s] = self.registered_dates.get(s, 0) + 1
#
s = cs.si.type
self.registered_types[s] = self.registered_types.get(s, 0) + 1
if self.registered_types[s] == 1:
self.registered_subtypes[s] = {}
ss = cs.si.subtype
self.registered_subtypes[s][ss] = \
self.registered_subtypes.get(s, 0).get(ss, 0) + 1
if s not in self.type_max_cards or self.type_max_cards[s] < cs.ncards:
self.type_max_cards[s] = cs.ncards
s = cs.si.size
self.registered_sizes[s] = self.registered_sizes.get(s, 0) + 1
cs.updateCardback()
ResourceManager.register(self, cs)
def identify_missing_cardsets(self):
missing = []
# This object should list the bare minimum cardset requirements
# for a PySol install that can play all games.
required_types = {
CSI.TYPE_FRENCH: {
CSI.SUBTYPE_JOKER_DECK
},
CSI.TYPE_HANAFUDA: {},
CSI.TYPE_TAROCK: {},
CSI.TYPE_MAHJONGG: {},
CSI.TYPE_HEXADECK: {},
CSI.TYPE_MUGHAL_GANJIFA: {},
CSI.TYPE_DASHAVATARA_GANJIFA: {},
CSI.TYPE_TRUMP_ONLY: {},
CSI.TYPE_PUZZLE: {
CSI.SUBTYPE_3X3,
CSI.SUBTYPE_4X4,
CSI.SUBTYPE_5X5,
CSI.SUBTYPE_6X6,
CSI.SUBTYPE_7X7,
CSI.SUBTYPE_8X8,
CSI.SUBTYPE_9X9,
CSI.SUBTYPE_10X10
},
CSI.TYPE_ISHIDO: {}
}
required_cards_needed = {
CSI.TYPE_TRUMP_ONLY: 100
}
for t in required_types.keys():
if t not in self.registered_types:
missing.append(CSI.TYPE_NAME[t])
else:
if len(required_types[t]) > 0:
for tt in required_types[t]:
if tt not in self.registered_subtypes[t]:
missing.append(CSI.TYPE_NAME[t] + " (" +
CSI.SUBTYPE_NAME[t][tt] + ")")
if t in required_cards_needed:
if self.type_max_cards[t] < required_cards_needed[t]:
missing.append(CSI.TYPE_NAME[t] + " (" +
_("With %(cards)d or more cards" + ")")
% {'cards': required_cards_needed[t]})
missing.sort()
return missing
# ************************************************************************
# * Tile
# ************************************************************************
# TableTileInfo constants
class TTI:
# tile size
SIZE_UNKNOWN = 0
SIZE_TILE = 1
SIZE_SD = 2
SIZE_HD = 3
SIZE_4K = 4
class Tile(Resource):
def __init__(self, **kw):
kw['color'] = None
kw['stretch'] = 0
kw['save_aspect'] = 0
kw['size'] = 0
Resource.__init__(self, **kw)
class TileManager(ResourceManager):
def register(self, tile):
if USE_PIL:
try:
img = Image.open(tile.filename)
TW, TH = img.size
if TW < 640 or TH < 480:
tile.size = TTI.SIZE_TILE
elif TW < 1280 or TH < 720:
tile.size = TTI.SIZE_SD
elif TW < 3840 or TH < 2160:
tile.size = TTI.SIZE_HD
else:
tile.size = TTI.SIZE_4K
except AttributeError:
tile.size = TTI.SIZE_UNKNOWN
else:
tile.size = TTI.SIZE_UNKNOWN
ResourceManager.register(self, tile)
# ************************************************************************
# * Sample
# ************************************************************************
class Sample(Resource):
def __init__(self, **kw):
kw['volume'] = -1
Resource.__init__(self, **kw)
class SampleManager(ResourceManager):
pass
# ************************************************************************
# * Music
# ************************************************************************
class Music(Sample):
pass
class MusicManager(SampleManager):
pass