1#!/usr/bin/env python
2# -*- mode: python; coding: utf-8; -*-
3# ---------------------------------------------------------------------------##
4#
5#  Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
6#  Copyright (C) 2003 Mt. Hood Playing Card Co.
7#  Copyright (C) 2005-2009 Skomoroh
8#
9#  This program is free software: you can redistribute it and/or modify
10#  it under the terms of the GNU General Public License as published by
11#  the Free Software Foundation, either version 3 of the License, or
12#  (at your option) any later version.
13#
14#  This program is distributed in the hope that it will be useful,
15#  but WITHOUT ANY WARRANTY; without even the implied warranty of
16#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17#  GNU General Public License for more details.
18#
19#  You should have received a copy of the GNU General Public License
20#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
21#
22# ---------------------------------------------------------------------------##
23
24
25import imp
26
27import pysollib.settings
28from pysollib.mfxutil import Struct, print_err
29from pysollib.mygettext import _, n_
30from pysollib.resource import CSI
31
32import six
33
34# ************************************************************************
35# * constants
36# ************************************************************************
37
38
39# GameInfo constants
40class GI:
41    # game category - these *must* match the cardset CSI.TYPE_xxx
42    GC_FRENCH = CSI.TYPE_FRENCH
43    GC_HANAFUDA = CSI.TYPE_HANAFUDA
44    GC_TAROCK = CSI.TYPE_TAROCK
45    GC_MAHJONGG = CSI.TYPE_MAHJONGG
46    GC_HEXADECK = CSI.TYPE_HEXADECK
47    GC_MUGHAL_GANJIFA = CSI.TYPE_MUGHAL_GANJIFA
48    GC_NAVAGRAHA_GANJIFA = CSI.TYPE_NAVAGRAHA_GANJIFA
49    GC_DASHAVATARA_GANJIFA = CSI.TYPE_DASHAVATARA_GANJIFA
50    GC_TRUMP_ONLY = CSI.TYPE_TRUMP_ONLY
51
52    # game type
53    GT_1DECK_TYPE = 0
54    GT_2DECK_TYPE = 1
55    GT_3DECK_TYPE = 2
56    GT_4DECK_TYPE = 3
57    GT_BAKERS_DOZEN = 4
58    GT_BELEAGUERED_CASTLE = 5
59    GT_CANFIELD = 6
60    GT_DASHAVATARA_GANJIFA = 7
61    GT_FAN_TYPE = 8
62    GT_FORTY_THIEVES = 9
63    GT_FREECELL = 10
64    GT_GOLF = 11
65    GT_GYPSY = 12
66    GT_HANAFUDA = 13
67    GT_HEXADECK = 14
68    GT_KLONDIKE = 15
69    GT_MAHJONGG = 16
70    GT_MATRIX = 17
71    GT_MEMORY = 18
72    GT_MONTANA = 19
73    GT_MUGHAL_GANJIFA = 20
74    GT_NAPOLEON = 21
75    GT_NAVAGRAHA_GANJIFA = 22
76    GT_NUMERICA = 23
77    GT_PAIRING_TYPE = 24
78    GT_POKER_TYPE = 25
79    GT_PUZZLE_TYPE = 26
80    GT_RAGLAN = 27
81    GT_ROW_TYPE = 28
82    GT_SIMPLE_TYPE = 29
83    GT_SPIDER = 30
84    GT_TAROCK = 31
85    GT_TERRACE = 32
86    GT_YUKON = 33
87    GT_SHISEN_SHO = 34
88    GT_HANOI = 35
89    GT_PEGGED = 36
90    GT_CRIBBAGE_TYPE = 37
91    GT_CUSTOM = 40
92    # extra flags
93    GT_BETA = 1 << 12      # beta version of game driver
94    GT_CHILDREN = 1 << 13      # *not used*
95    GT_CONTRIB = 1 << 14      # contributed games under the GNU GPL
96    GT_HIDDEN = 1 << 15      # not visible in menus, but games can be loaded
97    GT_OPEN = 1 << 16
98    GT_ORIGINAL = 1 << 17
99    GT_POPULAR = 1 << 18      # *not used*
100    GT_RELAXED = 1 << 19
101    GT_SCORE = 1 << 20      # game has some type of scoring
102    GT_SEPARATE_DECKS = 1 << 21
103    GT_XORIGINAL = 1 << 22      # original games by other people, not playable
104    # skill level
105    SL_LUCK = 1
106    SL_MOSTLY_LUCK = 2
107    SL_BALANCED = 3
108    SL_MOSTLY_SKILL = 4
109    SL_SKILL = 5
110    #
111    TYPE_NAMES = {
112        GT_BAKERS_DOZEN:        n_("Baker's Dozen"),
113        GT_BELEAGUERED_CASTLE:  n_("Beleaguered Castle"),
114        GT_CANFIELD:            n_("Canfield"),
115        GT_FAN_TYPE:            n_("Fan"),
116        GT_FORTY_THIEVES:       n_("Forty Thieves"),
117        GT_FREECELL:            n_("FreeCell"),
118        GT_GOLF:                n_("Golf"),
119        GT_GYPSY:               n_("Gypsy"),
120        GT_KLONDIKE:            n_("Klondike"),
121        GT_MONTANA:             n_("Montana"),
122        GT_NAPOLEON:            n_("Napoleon"),
123        GT_NUMERICA:            n_("Numerica"),
124        GT_PAIRING_TYPE:        n_("Pairing"),
125        GT_RAGLAN:              n_("Raglan"),
126        GT_SIMPLE_TYPE:         n_("Simple games"),
127        GT_SPIDER:              n_("Spider"),
128        GT_TERRACE:             n_("Terrace"),
129        GT_YUKON:               n_("Yukon"),
130        GT_1DECK_TYPE:          n_("One-Deck games"),
131        GT_2DECK_TYPE:          n_("Two-Deck games"),
132        GT_3DECK_TYPE:          n_("Three-Deck games"),
133        GT_4DECK_TYPE:          n_("Four-Deck games"),
134
135        GT_MAHJONGG:            n_("Mahjongg"),
136        GT_HANAFUDA:            n_("Hanafuda"),
137        GT_MUGHAL_GANJIFA:      n_("Mughal Ganjifa"),
138        GT_DASHAVATARA_GANJIFA: n_("Dashavatara Ganjifa"),
139
140        GT_CRIBBAGE_TYPE:       n_("Cribbage"),
141        GT_HEXADECK:            n_("Hex A Deck"),
142        GT_MATRIX:              n_("Matrix"),
143        GT_MEMORY:              n_("Memory"),
144        GT_PEGGED:              n_("Pegged"),
145        GT_POKER_TYPE:          n_("Poker"),
146        GT_SHISEN_SHO:          n_("Shisen-Sho"),
147        GT_TAROCK:              n_("Tarock"),
148        GT_HANOI:               n_("Tower of Hanoi"),
149
150        GT_CUSTOM:              n_("Custom"),
151    }
152
153    #      SELECT_GAME_BY_TYPE = []
154    #      for gt, name in TYPE_NAMES.items():
155    #          if not name.endswith('games'):
156    #              name = name+n_(' type')
157    #          SELECT_GAME_BY_TYPE.append(
158    #              (name, lambda gi, gt=gt: gi.si.game_type == gt))
159    #      SELECT_GAME_BY_TYPE = tuple(SELECT_GAME_BY_TYPE)
160
161    def _gen_select(title, game_type):
162        def _callback(gi, gt=game_type):
163            return gi.si.game_type == gt
164        return (title, _callback)
165
166    SELECT_GAME_BY_TYPE = (
167        _gen_select(title=n_("Baker's Dozen type"), game_type=GT_BAKERS_DOZEN),
168        _gen_select(
169            title=n_("Beleaguered Castle type"),
170            game_type=GT_BELEAGUERED_CASTLE),
171        _gen_select(title=n_("Canfield type"), game_type=GT_CANFIELD),
172        _gen_select(title=n_("Fan type"), game_type=GT_FAN_TYPE),
173        _gen_select(
174            title=n_("Forty Thieves type"), game_type=GT_FORTY_THIEVES),
175        _gen_select(title=n_("FreeCell type"), game_type=GT_FREECELL),
176        _gen_select(title=n_("Golf type"), game_type=GT_GOLF),
177        _gen_select(title=n_("Gypsy type"), game_type=GT_GYPSY),
178        _gen_select(title=n_("Klondike type"), game_type=GT_KLONDIKE),
179        _gen_select(title=n_("Montana type"), game_type=GT_MONTANA),
180        _gen_select(title=n_("Napoleon type"), game_type=GT_NAPOLEON),
181        _gen_select(title=n_("Numerica type"), game_type=GT_NUMERICA),
182        _gen_select(title=n_("Pairing type"), game_type=GT_PAIRING_TYPE),
183        _gen_select(title=n_("Raglan type"), game_type=GT_RAGLAN),
184        _gen_select(title=n_("Simple games"), game_type=GT_SIMPLE_TYPE),
185        _gen_select(title=n_("Spider type"), game_type=GT_SPIDER),
186        _gen_select(title=n_("Terrace type"), game_type=GT_TERRACE),
187        _gen_select(title=n_("Yukon type"), game_type=GT_YUKON),
188        _gen_select(title=n_("One-Deck games"), game_type=GT_1DECK_TYPE),
189        _gen_select(title=n_("Two-Deck games"), game_type=GT_2DECK_TYPE),
190        _gen_select(title=n_("Three-Deck games"), game_type=GT_3DECK_TYPE),
191        _gen_select(title=n_("Four-Deck games"), game_type=GT_4DECK_TYPE),
192    )
193
194    SELECT_ORIGINAL_GAME_BY_TYPE = (
195        (n_("French type"), lambda gi, gf=GT_ORIGINAL,
196            gt=(
197                GT_HANAFUDA,
198                GT_HEXADECK, GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA,
199                GT_DASHAVATARA_GANJIFA, GT_TAROCK,): gi.si.game_flags & gf and
200            gi.si.game_type not in gt),
201        (n_("Ganjifa type"), lambda gi, gf=GT_ORIGINAL,
202            gt=(GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA,
203                GT_DASHAVATARA_GANJIFA,): gi.si.game_flags & gf and
204            gi.si.game_type in gt),
205        (n_("Hanafuda type"), lambda gi, gf=GT_ORIGINAL, gt=GT_HANAFUDA:
206            gi.si.game_flags & gf and gi.si.game_type == gt),
207        (n_("Hex A Deck type"), lambda gi, gf=GT_ORIGINAL, gt=GT_HEXADECK:
208            gi.si.game_flags & gf and gi.si.game_type == gt),
209        (n_("Tarock type"), lambda gi, gf=GT_ORIGINAL, gt=GT_TAROCK:
210            gi.si.game_flags & gf and gi.si.game_type == gt),
211    )
212
213    SELECT_CONTRIB_GAME_BY_TYPE = (
214        (n_("French type"), lambda gi, gf=GT_CONTRIB,
215            gt=(GT_HANAFUDA, GT_HEXADECK, GT_MUGHAL_GANJIFA,
216                GT_NAVAGRAHA_GANJIFA, GT_DASHAVATARA_GANJIFA, GT_TAROCK,):
217            gi.si.game_flags & gf and gi.si.game_type not in gt),
218        (n_("Ganjifa type"), lambda gi, gf=GT_CONTRIB,
219            gt=(GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA,
220                GT_DASHAVATARA_GANJIFA,):
221            gi.si.game_flags & gf and gi.si.game_type in gt),
222        (n_("Hanafuda type"), lambda gi, gf=GT_CONTRIB, gt=GT_HANAFUDA:
223            gi.si.game_flags & gf and gi.si.game_type == gt),
224        (n_("Hex A Deck type"), lambda gi, gf=GT_CONTRIB, gt=GT_HEXADECK:
225            gi.si.game_flags & gf and gi.si.game_type == gt),
226        (n_("Tarock type"), lambda gi, gf=GT_CONTRIB, gt=GT_TAROCK:
227            gi.si.game_flags & gf and gi.si.game_type == gt),
228    )
229
230    SELECT_ORIENTAL_GAME_BY_TYPE = (
231        (n_("Dashavatara Ganjifa type"), lambda gi, gt=GT_DASHAVATARA_GANJIFA:
232            gi.si.game_type == gt),
233        (n_("Ganjifa type"), lambda gi,
234            gt=(GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA,
235                GT_DASHAVATARA_GANJIFA,): gi.si.game_type in gt),
236        (n_("Hanafuda type"),
237            lambda gi, gt=GT_HANAFUDA: gi.si.game_type == gt),
238        (n_("Mughal Ganjifa type"),
239            lambda gi, gt=GT_MUGHAL_GANJIFA: gi.si.game_type == gt),
240        (n_("Navagraha Ganjifa type"),
241            lambda gi, gt=GT_NAVAGRAHA_GANJIFA: gi.si.game_type == gt),
242    )
243
244    SELECT_SPECIAL_GAME_BY_TYPE = (
245        (n_("Cribbage type"),
246            lambda gi, gt=GT_CRIBBAGE_TYPE: gi.si.game_type == gt),
247        (n_("Hex A Deck type"),
248            lambda gi, gt=GT_HEXADECK: gi.si.game_type == gt),
249        (n_("Matrix type"), lambda gi, gt=GT_MATRIX: gi.si.game_type == gt),
250        (n_("Memory type"), lambda gi, gt=GT_MEMORY: gi.si.game_type == gt),
251        (n_("Pegged type"), lambda gi, gt=GT_PEGGED: gi.si.game_type == gt),
252        (n_("Poker type"), lambda gi, gt=GT_POKER_TYPE: gi.si.game_type == gt),
253        (n_("Puzzle type"),
254            lambda gi, gt=GT_PUZZLE_TYPE: gi.si.game_type == gt),
255        (n_("Shisen-Sho type"),
256            lambda gi, gt=GT_SHISEN_SHO: gi.si.game_type == gt),
257        (n_("Tarock type"), lambda gi, gt=GT_TAROCK: gi.si.game_type == gt),
258        (n_("Tower of Hanoi type"),
259            lambda gi, gt=GT_HANOI: gi.si.game_type == gt),
260    )
261
262    # These obsolete gameids have been used in previous versions of
263    # PySol and are no longer supported because of internal changes
264    # (mainly rule changes). The game has been assigned a new id.
265    PROTECTED_GAMES = {
266         22:  106,              # Double Canfield
267         32:  901,              # La Belle Lucie (Midnight Oil)
268         52:  903,              # Aces Up
269         72:  115,              # Little Forty
270         75:  126,              # Red and Black
271         82:  901,              # La Belle Lucie (Midnight Oil)
272         # 155: 5034,              # Mahjongg - Flying Dragon
273         # 156: 5035,              # Mahjongg - Fortress Towers
274         262:  105,              # Canfield
275         902:   88,              # Trefoil
276         904:   68,              # Lexington Harp
277         297:  631,              # Alternation/Alternations
278    }
279
280    # For games by compatibility, note that missing games may actually
281    # be present under alternate names.  This needs to be verified.
282    # If such a game is found, the alternate name should be added if
283    # possible, and the game recorded in the compatibility section
284    # appropriately.
285    #
286    # Note that there are instances where another program's
287    # implementation of a game uses different rules than PySol, or
288    # has a different game with the same name.  These are marked
289    # as missing.
290    #
291    # If a game is listed as missing from multiple collections below,
292    # adding it should be a priority.
293
294    GAMES_BY_COMPATIBILITY = (
295        # Atari ST Patience game v2.13 (we have 10 out of 10 games)
296        ("Atari ST Patience", (1, 3, 4, 7, 12, 14, 15, 16, 17, 39,)),
297
298        #  Gnome AisleRiot 1.0.51 (we have 28 out of 32 games)
299        #    still missing: Camelot, Clock, Thieves, Thirteen
300        # ("Gnome AisleRiot 1.0.51", (
301        #     2, 8, 11, 19, 27, 29, 33, 34, 35, 40,
302        #     41, 42, 43, 58, 59, 92, 93, 94, 95, 96,
303        #     100, 105, 111, 112, 113, 130, 200, 201,
304        # )),
305        #  Gnome AisleRiot 1.4.0.1 (we have XX out of XX games)
306        # ("Gnome AisleRiot", (
307        #     1, 2, 8, 11, 19, 27, 29, 33, 34, 35, 40,
308        #     41, 42, 43, 58, 59, 92, 93, 94, 95, 96,
309        #     100, 105, 111, 112, 113, 130, 200, 201,
310        # )),
311        # Gnome AisleRiot 2.2.0 (we have 65 out of 70 games)
312        # Gnome AisleRiot 3.22.7
313        #   still missing:
314        #       Block Ten, Hamilton, Isabel, King's Audience, Labyrinth,
315        #       Napoleon's Tomb, Saratoga, Thieves, Treize, Valentine,
316        #       Wall
317        ("Gnome AisleRiot", (
318            1, 2, 8, 9, 11, 12, 13, 19, 24, 27, 29, 31, 33, 34, 35, 36,
319            38, 40, 41, 42, 43, 45, 48, 58, 59, 60, 65, 67, 89, 91, 92,
320            93, 94, 95, 96, 97, 100, 104, 105, 111, 112, 113, 130, 135,
321            139, 144, 146, 147, 148, 200, 201, 206, 224, 225, 229, 230,
322            233, 257, 258, 280, 281, 282, 283, 284, 334, 384, 495, 551,
323            552, 553, 572, 593, 674, 700, 737, 772, 810, 819, 824, 22231,
324        )),
325
326        #  KDE Patience 0.7.3 from KDE 1.1.2 (we have 6 out of 9 games)
327        # ("KDE Patience 0.7.3", (2, 7, 8, 18, 256, 903,)),
328        #  KDE Patience 2.0 from KDE 2.1.2 (we have 11 out of 13 games)
329        # ("KDE Patience", (1, 2, 7, 8, 18, 19, 23, 50, 256, 261, 903,)),
330        #  KDE Patience 2.0 from KDE 2.2beta1 (we have 12 out of 14 games)
331        # ("KDE Patience", (1, 2, 7, 8, 18, 19, 23, 36, 50, 256, 261, 903,)),
332        # KDE Patience 2.0 from KDE 3.1.1 (we have 15 out of 15 games)
333        ("KDE Patience", (1, 2, 7, 8, 18, 19, 23, 36, 50,
334                          256, 261, 277, 278, 279, 903,)),
335
336        # Microsoft Solitaire (we have all 5 games)
337        ("Microsoft Solitaire Collection", (2, 8, 11, 38, 22231,)),
338
339        # XM Solitaire
340        # still missing:
341        #       Ace of Hearts, Affinity, Agnes Three, Antares, Archway,
342        #       Avenue, Baker's Fan, Baker's Spider, Bedeviled, Binding,
343        #       Black Holes, Black Spider, Block Ten, California,
344        #       Carcassone, Cascade, Club, Color Cell, Cornelius,
345        #       Demons and Thieves, Desert Fox, Deuces and Queens,
346        #       Double Antares, Double Antarctica, Double Arctica,
347        #       Double Baker's Spider, Double Cascade, Double Fourteens,
348        #       Double Line 8, Double Majesty, Double Sea Towers,
349        #       Double Spidercells, Doublet Cell 5, Doubt, Dream Fan,
350        #       Dumfries Cell, Falcon Wing, Fan Nine, Fanny 6,
351        #       Four By Ten, FreeCell AK, Gaps Alter, Gaps Diff,
352        #       George V, Grandmother's Clock, In a Frame, Inverted FreeCell,
353        #       Kings, Klondike FreeCell, La Cabane, La Double Entente,
354        #       Little Gazette, Magic FreeCell, Mini Gaps, Montreal,
355        #       Napoleon at Iena, Napoleon at Waterloo, Napoleon's Guards,
356        #       Nationale, Oasis, Opera, Ordered Suits, Osmotic FreeCell,
357        #       Pair FreeCell, Pairs 2, Petal, Rainbow Fan, Reserved Thirteens,
358        #       Sea Spider, Sept Piles 0, Short Solitaire, Simple Alternations,
359        #       Simple Spark, Step By Step, Strategy 7, Stripped FreeCell,
360        #       Tarantula, Triple Dispute, Trusty Twenty, Two Ways 3,
361        #       Up Or Down, Versailles, Vertical FreeCell, Wasp Baby,
362        #       Yukon FreeCell
363        ("XM Solitaire", (
364            2, 8, 9, 13, 15, 18, 19, 20, 29, 30, 31, 34, 36, 38, 41, 42,
365            45, 46, 50, 53, 54, 56, 57, 59, 64, 77, 78, 86, 96, 97, 98,
366            105, 110, 112, 124, 145, 220, 222, 223, 224, 228, 231, 233,
367            234, 235, 236, 257, 258, 264, 265, 267, 270, 271, 290, 291,
368            292, 303, 309, 314, 318, 320, 322, 324, 325, 336, 338, 341,
369            363, 364, 372, 376, 383, 384, 385, 386, 390, 391, 393, 398,
370            405, 415, 416, 425, 451, 453, 461, 464, 466, 467, 476, 480,
371            484, 511, 512, 516, 561, 610, 625, 629, 631, 638, 641, 647,
372            650, 655, 678, 734, 751, 784, 825, 901,
373        )),
374
375        # xpat2 1.06 (we have 14 out of 16 games)
376        #   still missing: Michael's Fantasy, modCanfield
377        ("xpat2", (
378            1, 2, 8, 9, 11, 31, 54, 63, 89, 105, 901, 256, 345, 903,
379        )),
380    )
381
382    GAMES_BY_INVENTORS = (
383        ("Paul Alfille", (8,)),
384        ("C.L. Baker", (45,)),
385        ("David Bernazzani", (314,)),
386        ("Gordon Bower", (763, 783,)),
387        ("Art Cabral", (9,)),
388        ("Richard A. Canfield", (105,)),
389        ("Robert Harbin", (381,)),
390        ("Robert Hogue", (22216, 22217, 22218, 22231,)),
391        ("Charles Jewell", (220, 309,)),
392        ("Michael Keller", (592,)),
393        ("Fred Lunde", (459,)),
394        ("Albert Morehead and Geoffrey Mott-Smith", (25, 42, 48, 173, 282,
395                                                     303, 362, 547, 738)),
396        ("Toby Ord", (788,)),
397        ("David Parlett", (64, 98, 294, 338, 654, 796, 812)),
398        ("Randy Rasa", (187, 190, 191, 192,)),
399        ("Adam Selene", (366,)),
400        ("Jim Sizelove", (555001,)),
401        ("Captain Jeffrey T. Spaulding", (400,)),
402        ("John Stoneham", (201,)),
403        ("Bryan Stout", (655,)),
404        ("Bill Taylor", (349,)),
405        ("Thomas Warfield", (189, 264, 300, 320, 336, 337, 359,
406                             415, 427, 458, 495, 496, 497, 508,
407                             800, 814, 820, 825,)),
408        )
409
410    GAMES_BY_PYSOL_VERSION = (
411        ("1.00", (1, 2, 3, 4)),
412        ("1.01", (5, 6)),
413        ("1.02", (7, 8, 9)),
414        ("1.03", (10, 11, 12, 13)),
415        ("1.10", (14,)),
416        ("1.11", (15, 16, 17)),
417        ("2.00", (256, 257)),
418        ("2.01", (258, 259, 260, 261)),
419        ("2.02", (105,)),
420        ("2.90", (18, 19, 20, 21, 106, 23, 24, 25, 26, 27,
421                  28, 29, 30, 31, 901, 33, 34, 35, 36)),
422        ("2.99", (37,)),
423        ("3.00", (38, 39,
424                  40, 41, 42, 43,     45, 46, 47, 48, 49,
425                  50, 51, 903, 53, 54, 55, 56, 57, 58, 59,
426                  60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
427                  70, 71, 115, 73, 74, 126, 76, 77, 78, 79,
428                  80, 81,     83, 84, 85, 86, 87, 88, 89,
429                  90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
430                  100, 101, 102, 103, 104, 107, 108,)),
431        ("3.10", (109, 110, 111, 112, 113, 114, 116, 117, 118, 119,
432                  120, 121, 122, 123, 124, 125, 127)),
433        ("3.20", (128, 129, 130, 131, 132, 133, 134, 135, 136, 137,
434                  138, 139, 140, 141, 142,
435                  12345, 12346, 12347, 12348, 12349, 12350, 12351, 12352)),
436        ("3.21", (143, 144)),
437        ("3.30", (145, 146, 147, 148, 149, 150, 151)),
438        ("3.40", (152, 153, 154)),
439        ("4.00", (157, 158, 159, 160, 161, 162, 163, 164)),
440        ("4.20", (165, 166, 167, 168, 169, 170, 171, 172, 173, 174,
441                  175, 176, 177, 178)),
442        ("4.30", (179, 180, 181, 182, 183, 184)),
443        ("4.41", (185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
444                  195, 196, 197, 198, 199)),
445        ("4.60", (200, 201, 202, 203, 204, 205,
446                  206, 207, 208, 209,
447                  210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
448                  220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
449                  230, 231, 232, 233, 234, 235, 236)),
450        ("4.70", (237,)),
451        ('fc-0.5.0', (  # moved from Ultrasol
452                      # 121, 122, 187, 188, 189, 190, 191, 192, 194, 197, 198,
453                      5301, 5302, 9011, 11001, 11002, 11003, 11004, 11005,
454                      11006, 12353, 12354, 12355, 12356, 12357, 12358, 12359,
455                      12360, 12361, 12362, 12363, 12364, 12365, 12366, 12367,
456                      12368, 12369, 12370, 12371, 12372, 12373, 12374, 12375,
457                      12376, 12377, 12378, 12379, 12380, 12381, 12382, 12383,
458                      12384, 12385, 13001, 13002, 13003, 13004, 13005, 13006,
459                      13007, 13008, 13009, 13010, 13011, 13012, 13013, 13014,
460                      13163, 13164, 13165, 13166, 13167, 14401, 14402, 14403,
461                      14404, 14405, 14406, 14407, 14408, 14409, 14410, 14411,
462                      14412, 14413, 15406, 15407, 15408, 15409, 15410, 15411,
463                      15412, 15413, 15414, 15415, 15416, 15417, 15418, 15419,
464                      15420, 15421, 15422, 16000, 16001, 16002, 16003, 16004,
465                      16666, 16667, 16668, 16669, 16670, 16671, 16672, 16673,
466                      16674, 16675, 16676, 16677, 16678, 16679, 16680, 22216,
467                      22223, 22224, 22225, 22226, 22227, 22228, 22229, 22230,
468                      22231, 22232,)),
469        ('fc-0.8.0', tuple(range(263, 323))),  # exclude 297
470        ('fc-0.9.0', tuple(range(323, 421))),
471        ('fc-0.9.1', tuple(range(421, 441))),
472        ('fc-0.9.2', tuple(range(441, 466))),
473        ('fc-0.9.3', tuple(range(466, 661))),
474        ('fc-0.9.4', tuple(range(661, 671))),
475        ('fc-1.0',   tuple(range(671, 711))),
476        ('fc-1.1',   tuple(range(711, 759))),
477        ('fc-2.0',   tuple(range(11011, 11014)) + tuple(range(759, 767))),
478        ('fc-2.1',   tuple(range(767, 774)) + (555001,)),
479        ('fc-2.8',   (343001,)),
480        ('fc-2.12',   tuple(range(774, 811)) + (16681,) +
481         tuple(range(22217, 22219))),
482        ('fc-2.14', tuple(range(811, 827)))
483    )
484
485    # deprecated - the correct way is to or a GI.GT_XXX flag
486    # in the registerGame() call
487    _CHILDREN_GAMES = [16, 33, 55, 90, 91, 96, 97, 176, 903, ]
488
489    _OPEN_GAMES = []
490
491    _POPULAR_GAMES = [
492        1,     # Gypsy
493        2,     # Klondike
494        7,     # Picture Galary
495        8,     # FreeCell
496        9,     # Seahaven Towers
497        11,    # Spider
498        12,    # Braid
499        13,    # Forty Thieves
500        14,    # Grounds for a Divorce
501        19,    # Yukon
502        31,    # Baker's Dozen
503        36,    # Golf
504        38,    # Pyramid
505        105,   # Canfield
506        158,   # Imperial Trumps
507        279,   # Kings
508        903,   # Ace Up
509        5034,  # Mahjongg Flying Dragon
510        5401,  # Mahjongg Taipei
511        12345,  # Oonsoo
512    ]
513
514
515# ************************************************************************
516# * core games database
517# ************************************************************************
518
519class GameInfoException(Exception):
520    pass
521
522
523class GameInfo(Struct):
524    def __init__(self, id, gameclass, name,
525                 game_type, decks, redeals,
526                 skill_level=None,
527                 # keyword arguments:
528                 si={}, category=0,
529                 short_name=None, altnames=(),
530                 suits=list(range(4)), ranks=list(range(13)), trumps=(),
531                 rules_filename=None,
532                 ):
533        #
534        def to_unicode(s):
535            if isinstance(s, six.text_type):
536                return s
537            try:
538                s = six.text_type(s, 'utf-8')
539            except UnicodeDecodeError as err:
540                print_err(err)
541                s = six.text_type(s, 'utf-8', 'ignore')
542            return s
543        ncards = decks * (len(suits) * len(ranks) + len(trumps))
544        game_flags = game_type & ~1023
545        game_type = game_type & 1023
546        name = to_unicode(name)
547        en_name = name                  # for app.getGameRulesFilename
548        if pysollib.settings.TRANSLATE_GAME_NAMES:
549            name = _(name)
550        if not short_name:
551            short_name = name
552        else:
553            short_name = to_unicode(short_name)
554            if pysollib.settings.TRANSLATE_GAME_NAMES:
555                short_name = _(short_name)
556        if isinstance(altnames, six.string_types):
557            altnames = (altnames,)
558        altnames = [to_unicode(n) for n in altnames]
559        if pysollib.settings.TRANSLATE_GAME_NAMES:
560            altnames = [_(n) for n in altnames]
561        #
562        if not (1 <= category <= 9):
563            if game_type == GI.GT_HANAFUDA:
564                category = GI.GC_HANAFUDA
565            elif game_type == GI.GT_TAROCK:
566                category = GI.GC_TAROCK
567            elif game_type == GI.GT_MAHJONGG:
568                category = GI.GC_MAHJONGG
569            elif game_type == GI.GT_HEXADECK:
570                category = GI.GC_HEXADECK
571            elif game_type == GI.GT_MUGHAL_GANJIFA:
572                category = GI.GC_MUGHAL_GANJIFA
573            elif game_type == GI.GT_NAVAGRAHA_GANJIFA:
574                category = GI.GC_NAVAGRAHA_GANJIFA
575            elif game_type == GI.GT_DASHAVATARA_GANJIFA:
576                category = GI.GC_DASHAVATARA_GANJIFA
577            else:
578                category = GI.GC_FRENCH
579        #
580        if not (1 <= id <= 999999):
581            raise GameInfoException(name+": invalid game ID "+str(id))
582        if category == GI.GC_MAHJONGG:
583            if decks % 4:
584                raise GameInfoException(name+": invalid number of decks " +
585                                        str(id))
586        else:
587            if not (1 <= decks <= 4):
588                raise GameInfoException(
589                    name+": invalid number of decks "+str(id))
590        if not name:
591            raise GameInfoException(name+": invalid game name")
592        if GI.PROTECTED_GAMES.get(id):
593            raise GameInfoException(name+": protected game ID "+str(id))
594        #
595        for f, l in ((GI.GT_CHILDREN, GI._CHILDREN_GAMES),
596                     (GI.GT_OPEN, GI._OPEN_GAMES),
597                     (GI.GT_POPULAR, GI._POPULAR_GAMES)):
598            if (game_flags & f) and (id not in l):
599                l.append(id)
600            elif not (game_flags & f) and (id in l):
601                game_flags = game_flags | f
602        # si is the SelectionInfo struct that will be queried by
603        # the "select game" dialogs. It can be freely modified.
604        gi_si = Struct(game_type=game_type, game_flags=game_flags,
605                       decks=decks, redeals=redeals, ncards=ncards)
606        gi_si.update(si)
607        #
608        Struct.__init__(self, id=id, gameclass=gameclass,
609                        name=name, short_name=short_name,
610                        altnames=tuple(altnames), en_name=en_name,
611                        decks=decks, redeals=redeals, ncards=ncards,
612                        category=category, skill_level=skill_level,
613                        suits=tuple(suits), ranks=tuple(ranks),
614                        trumps=tuple(trumps),
615                        si=gi_si, rules_filename=rules_filename)
616
617
618class GameManager:
619    def __init__(self):
620        self.__selected_key = -1
621        self.__games = {}
622        self.__gamenames = {}
623        self.__games_by_id = None
624        self.__games_by_name = None
625        self.__games_by_short_name = None
626        self.__games_by_altname = None
627        self.__all_games = {}           # includes hidden games
628        self.__all_gamenames = {}       # includes hidden games
629        self.__games_for_solver = []
630        self.check_game = True
631        self.current_filename = None
632        self.registered_game_types = {}
633        self.callback = None            # update progress-bar (see main.py)
634        self._num_games = 0             # for callback only
635
636    def setCallback(self, func):
637        self.callback = func
638
639    def getSelected(self):
640        return self.__selected_key
641
642    def setSelected(self, gameid):
643        assert gameid in self.__all_games
644        self.__selected_key = gameid
645
646    def get(self, key):
647        return self.__all_games.get(key)
648
649    def _check_game(self, gi):
650        # print 'check game:', gi.id, gi.short_name.encode('utf-8')
651        if gi.id in self.__all_games:
652            raise GameInfoException("duplicate game ID %s: %s and %s" %
653                                    (gi.id, str(gi.gameclass),
654                                     str(self.__all_games[gi.id].gameclass)))
655        if gi.name in self.__all_gamenames:
656            gameclass = self.__all_gamenames[gi.name].gameclass
657            raise GameInfoException("duplicate game name %s: %s and %s" %
658                                    (gi.name, str(gi.gameclass),
659                                     str(gameclass)))
660        if 1:
661            for id, game in self.__all_games.items():
662                if gi.gameclass is game.gameclass:
663                    raise GameInfoException(
664                        "duplicate game class %s: %s and %s" %
665                        (gi.id, str(gi.gameclass), str(game.gameclass)))
666        for n in gi.altnames:
667            if n in self.__all_gamenames:
668                raise GameInfoException("duplicate game altname %s: %s" %
669                                        (gi.id, n))
670
671    def register(self, gi):
672        # print gi.id, gi.short_name.encode('utf-8')
673        if not isinstance(gi, GameInfo):
674            raise GameInfoException("wrong GameInfo class")
675        if self.check_game and pysollib.settings.CHECK_GAMES:
676            self._check_game(gi)
677        # if 0 and gi.si.game_flags & GI.GT_XORIGINAL:
678        #     return
679        # print gi.id, gi.name
680        self.__all_games[gi.id] = gi
681        self.__all_gamenames[gi.name] = gi
682        for n in gi.altnames:
683            self.__all_gamenames[n] = gi
684        if not (gi.si.game_flags & GI.GT_HIDDEN):
685            self.__games[gi.id] = gi
686            self.__gamenames[gi.name] = gi
687            for n in gi.altnames:
688                self.__gamenames[n] = gi
689            # invalidate sorted lists
690            self.__games_by_id = None
691            self.__games_by_name = None
692            # update registry
693            k = gi.si.game_type
694            self.registered_game_types[k] = \
695                self.registered_game_types.get(k, 0) + 1
696#              if not gi.si.game_type == GI.GT_MAHJONGG:
697#                  for v, k in GI.GAMES_BY_PYSOL_VERSION:
698#                      if gi.id in k: break
699#                  else:
700#                      print gi.id
701            if hasattr(gi.gameclass, 'Solver_Class') and \
702               gi.gameclass.Solver_Class is not None:
703                self.__games_for_solver.append(gi.id)
704        if self.current_filename is not None:
705            gi.gameclass.MODULE_FILENAME = self.current_filename
706
707        if self.callback and self._num_games % 10 == 0:
708            self.callback()
709        self._num_games += 1
710
711    #
712    # access games database - we do not expose hidden games
713    #
714
715    def getAllGames(self):
716        # return self.__all_games
717        return list(self.__games.values())
718
719    def getGamesIdSortedById(self):
720        if self.__games_by_id is None:
721            lst = list(self.__games.keys())
722            lst.sort()
723            self.__games_by_id = tuple(lst)
724        return self.__games_by_id
725
726    def getGamesIdSortedByName(self):
727        if self.__games_by_name is None:
728            l1, l2, l3 = [], [], []
729            for id, gi in self.__games.items():
730                name = gi.name  # .lower()
731                l1.append((name, id))
732                if gi.name != gi.short_name:
733                    name = gi.short_name  # .lower()
734                l2.append((name, id))
735                for n in gi.altnames:
736                    name = n  # .lower()
737                    l3.append((name, id, n))
738            l1.sort()
739            l2.sort()
740            l3.sort()
741            self.__games_by_name = tuple([i[1] for i in l1])
742            self.__games_by_short_name = tuple([i[1] for i in l2])
743            self.__games_by_altname = tuple([i[1:] for i in l3])
744        return self.__games_by_name
745
746    def getGamesIdSortedByShortName(self):
747        if self.__games_by_name is None:
748            self.getGamesIdSortedByName()
749        return self.__games_by_short_name
750
751    # note: this contains tuples as entries
752    def getGamesTuplesSortedByAlternateName(self):
753        if self.__games_by_name is None:
754            self.getGamesIdSortedByName()
755        return self.__games_by_altname
756
757    # find game by name
758    def getGameByName(self, name):
759        gi = self.__all_gamenames.get(name)
760        if gi:
761            return gi.id
762        return None
763
764    def getGamesForSolver(self):
765        return self.__games_for_solver
766
767
768# ************************************************************************
769# *
770# ************************************************************************
771
772# the global game database (the single instance of class GameManager)
773GAME_DB = GameManager()
774
775
776def registerGame(gameinfo):
777    GAME_DB.register(gameinfo)
778    return gameinfo
779
780
781def hideGame(game):
782    game.gameinfo.si.game_type = GI.GT_HIDDEN
783    registerGame(game.gameinfo)
784
785
786def loadGame(modname, filename, check_game=False):
787    # print "load game", modname, filename
788    GAME_DB.check_game = check_game
789    GAME_DB.current_filename = filename
790    imp.load_source(modname, filename)
791    # execfile(filename, globals(), globals())
792    GAME_DB.current_filename = None
793