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