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 24import math 25 26from pysollib.game import Game 27from pysollib.gamedb import GI, GameInfo, registerGame 28from pysollib.hint import CautiousDefaultHint, DefaultHint 29from pysollib.layout import Layout 30from pysollib.mfxutil import kwdefault 31from pysollib.mygettext import _ 32from pysollib.pysoltk import MfxCanvasText 33from pysollib.stack import \ 34 AbstractFoundationStack, \ 35 BasicRowStack, \ 36 DealRowRedealTalonStack, \ 37 OpenStack, \ 38 ReserveStack, \ 39 SS_FoundationStack, \ 40 SS_RowStack, \ 41 Stack, \ 42 StackWrapper, \ 43 WasteStack, \ 44 WasteTalonStack 45from pysollib.util import ACE, KING, NO_RANK, RANKS, UNLIMITED_CARDS 46 47 48class Braid_Hint(DefaultHint): 49 # FIXME: demo is not too clever in this game 50 pass 51 52# ************************************************************************ 53# * 54# ************************************************************************ 55 56 57class Braid_Foundation(AbstractFoundationStack): 58 def __init__(self, x, y, game, suit, **cap): 59 kwdefault(cap, mod=13, dir=0, base_rank=NO_RANK, max_move=0) 60 AbstractFoundationStack.__init__(self, x, y, game, suit, **cap) 61 62 def acceptsCards(self, from_stack, cards): 63 if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): 64 return False 65 if not self.cards: 66 return True 67 stack_dir = self.game.getFoundationDir() 68 if stack_dir == 0: 69 card_dir = self.getRankDir(cards=(self.cards[-1], cards[0])) 70 return card_dir in (1, -1) 71 else: 72 return ((self.cards[-1].rank + stack_dir) % 73 self.cap.mod == cards[0].rank) 74 75 76class Braid_BraidStack(OpenStack): 77 def __init__(self, x, y, game, sine=0): 78 OpenStack.__init__(self, x, y, game) 79 self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET 80 CW = self.game.app.images.CARDW 81 if sine: 82 # use a sine wave for the x offsets 83 self.CARD_XOFFSET = [] 84 n = 9 85 dx = 0.4 * CW * (2*math.pi/n) 86 last_x = 0 87 for i in range(n): 88 x = int(round(dx * math.sin(i + 1))) 89 # print x, x - last_x 90 self.CARD_XOFFSET.append(x - last_x) 91 last_x = x 92 else: 93 self.CARD_XOFFSET = (-0.45*CW, 0.35*CW, 0.55*CW, -0.45*CW) 94 95 96class Braid_RowStack(ReserveStack): 97 def fillStack(self): 98 if not self.cards and self.game.s.braid.cards: 99 self.game.moveMove(1, self.game.s.braid, self) 100 101 getBottomImage = Stack._getBraidBottomImage 102 103 104class Braid_ReserveStack(ReserveStack): 105 def acceptsCards(self, from_stack, cards): 106 if from_stack is self.game.s.braid or from_stack in self.game.s.rows: 107 return False 108 return ReserveStack.acceptsCards(self, from_stack, cards) 109 110 getBottomImage = Stack._getTalonBottomImage 111 112 113# ************************************************************************ 114# * Braid 115# ************************************************************************ 116 117class Braid(Game): 118 Hint_Class = Braid_Hint 119 Foundation_Classes = [Braid_Foundation, Braid_Foundation] 120 121 BRAID_CARDS = 20 122 RANKS = RANKS # pull into class Braid 123 124 # 125 # game layout 126 # 127 128 def createGame(self): 129 # create layout 130 l, s = Layout(self), self.s 131 font = self.app.getFont("canvas_default") 132 133 # set window 134 # (piles up to 20 cards are playable - needed for Braid_BraidStack) 135 decks = self.gameinfo.decks 136 h = max(4*l.YS + 30, l.YS+(self.BRAID_CARDS-1)*l.YOFFSET) 137 self.setSize(l.XM+(8+decks)*l.XS, l.YM+h) 138 139 # extra settings 140 self.base_card = None 141 142 # create stacks 143 s.addattr(braid=None) # register extra stack variable 144 x, y = l.XM, l.YM 145 for i in range(2): 146 s.rows.append(Braid_RowStack(x + 0.5*l.XS, y, self)) 147 s.rows.append(Braid_RowStack(x + 4.5*l.XS, y, self)) 148 y = y + 3 * l.YS 149 y = l.YM + l.YS 150 for i in range(2): 151 s.rows.append(Braid_ReserveStack(x, y, self)) 152 s.rows.append(Braid_ReserveStack(x + l.XS, y, self)) 153 s.rows.append(Braid_ReserveStack(x, y + l.YS, self)) 154 s.rows.append(Braid_ReserveStack(x + l.XS, y + l.YS, self)) 155 x = x + 4 * l.XS 156 x, y = l.XM + l.XS * 5//2, l.YM 157 s.braid = Braid_BraidStack(x, y, self) 158 x, y = l.XM + 7 * l.XS, l.YM + l.YS * 3//2 159 s.talon = WasteTalonStack(x, y, self, max_rounds=3) 160 l.createText(s.talon, "s") 161 l.createRoundText(s.talon, 'nn') 162 x -= l.XS 163 s.waste = WasteStack(x, y, self) 164 l.createText(s.waste, "s") 165 y = l.YM 166 for i in range(4): 167 x = l.XM+8*l.XS 168 for cl in self.Foundation_Classes: 169 s.foundations.append(cl(x, y, self, suit=i)) 170 x += l.XS 171 y = y + l.YS 172 x = l.XM+8*l.XS+decks*l.XS//2 173 self.texts.info = MfxCanvasText(self.canvas, 174 x, y, anchor="n", font=font) 175 176 # define stack-groups 177 self.sg.talonstacks = [s.talon] + [s.waste] 178 self.sg.openstacks = s.foundations + s.rows 179 self.sg.dropstacks = [s.braid] + s.rows + [s.waste] 180 181 # 182 # game overrides 183 # 184 185 def _shuffleHook(self, cards): 186 # do not play a trump as the base_card 187 n = m = -1 - self.BRAID_CARDS - len(self.s.rows) 188 while cards[n].suit >= len(self.gameinfo.suits): 189 n = n - 1 190 cards[n], cards[m] = cards[m], cards[n] 191 return cards 192 193 def startGame(self): 194 self.base_card = None 195 self.updateText() 196 self.startDealSample() 197 for i in range(self.BRAID_CARDS): 198 self.s.talon.dealRow(rows=[self.s.braid], frames=4) 199 self.s.talon.dealRow(frames=4) 200 # deal base_card to foundations 201 self.base_card = self.s.talon.cards[-1] 202 to_stack = self.s.foundations[self.gameinfo.decks*self.base_card.suit] 203 self.flipMove(self.s.talon) 204 self.moveMove(1, self.s.talon, to_stack) 205 self.updateText() 206 for s in self.s.foundations: 207 s.cap.base_rank = self.base_card.rank 208 # deal first card to WasteStack 209 self.s.talon.dealCards() 210 211 shallHighlightMatch = Game._shallHighlightMatch_SSW 212 213 def getHighlightPilesStacks(self): 214 return () 215 216 def _restoreGameHook(self, game): 217 self.base_card = self.cards[game.loadinfo.base_card_id] 218 for s in self.s.foundations: 219 s.cap.base_rank = self.base_card.rank 220 221 def _loadGameHook(self, p): 222 self.loadinfo.addattr(base_card_id=None) # register extra load var. 223 self.loadinfo.base_card_id = p.load() 224 225 def _saveGameHook(self, p): 226 p.dump(self.base_card.id) 227 228 # 229 # game extras 230 # 231 232 def updateText(self): 233 if self.preview > 1 or not self.texts.info: 234 return 235 if not self.base_card: 236 t = "" 237 else: 238 t = self.RANKS[self.base_card.rank] 239 dir = self.getFoundationDir() 240 if dir == 1: 241 t = t + _(" Ascending") 242 elif dir == -1: 243 t = t + _(" Descending") 244 self.texts.info.config(text=t) 245 246 247class LongBraid(Braid): 248 BRAID_CARDS = 24 249 250 251# ************************************************************************ 252# * Fort 253# ************************************************************************ 254 255class Fort(Braid): 256 257 Foundation_Classes = [SS_FoundationStack, 258 StackWrapper(SS_FoundationStack, base_rank=KING, 259 dir=-1)] 260 261 BRAID_CARDS = 21 262 263 def _shuffleHook(self, cards): 264 # move 4 Kings and 4 Aces to top of the Talon 265 # (i.e. first cards to be dealt) 266 return self._shuffleHookMoveToTop( 267 cards, 268 lambda c: (c.rank in (ACE, KING) and c.deck == 0, 269 (c.suit, c.rank))) 270 271 def _restoreGameHook(self, game): 272 pass 273 274 def _loadGameHook(self, p): 275 pass 276 277 def _saveGameHook(self, p): 278 pass 279 280 def startGame(self): 281 self.s.talon.dealRow(rows=self.s.foundations, frames=0) 282 self.startDealSample() 283 for i in range(self.BRAID_CARDS): 284 self.s.talon.dealRow(rows=[self.s.braid], frames=4) 285 self.s.talon.dealRow(frames=4) 286 self.s.talon.dealCards() 287 288 289# ************************************************************************ 290# * Backbone 291# ************************************************************************ 292 293class Backbone_BraidStack(OpenStack): 294 def __init__(self, x, y, game, **cap): 295 OpenStack.__init__(self, x, y, game, **cap) 296 self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET 297 298 def basicIsBlocked(self): 299 return len(self.game.s.reserves[2].cards) != 0 300 301 302class Backbone(Game): 303 RESERVE_CARDS = 11 304 305 Hint_Class = CautiousDefaultHint 306 307 def createGame(self, rows=8): 308 # create layout 309 l, s = Layout(self), self.s 310 311 # set window 312 w, h = l.XM+(rows+2)*l.XS, max( 313 l.YM + 3 * l.XS + 10 * l.YOFFSET, 314 l.YM + 2 * l.YS + self.RESERVE_CARDS * l.YOFFSET + l.TEXT_HEIGHT) 315 self.setSize(w, h) 316 317 # create stacks 318 y = l.YM 319 for i in range(4): 320 x = l.XM+(rows-8)*l.XS//2 + i*l.XS 321 s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) 322 x = l.XM+(rows//2+2)*l.XS + i*l.XS 323 s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) 324 325 x, y = l.XM+rows*l.XS//2, l.YM 326 s.reserves.append(Backbone_BraidStack(x, y, self, max_accept=0)) 327 x += l.XS 328 s.reserves.append(Backbone_BraidStack(x, y, self, max_accept=0)) 329 x, y = l.XM+(rows+1)*l.XS//2, l.YM+self.RESERVE_CARDS*l.YOFFSET 330 s.reserves.append(BasicRowStack(x, y, self, max_accept=0)) 331 332 x, y = l.XM, l.YM+l.YS 333 for i in range(rows//2): 334 s.rows.append(SS_RowStack(x, y, self, max_move=1)) 335 x += l.XS 336 x, y = l.XM+(rows//2+2)*l.XS, l.YM+l.YS 337 for i in range(rows//2): 338 s.rows.append(SS_RowStack(x, y, self, max_move=1)) 339 x += l.XS 340 341 x, y = l.XM+rows*l.XS//2, h-l.YS 342 s.talon = WasteTalonStack(x, y, self, max_rounds=1) 343 l.createText(s.talon, "n") 344 x += l.XS 345 s.waste = WasteStack(x, y, self) 346 l.createText(s.waste, "n") 347 348 # define stack-groups 349 l.defaultStackGroups() 350 351 def startGame(self): 352 for i in range(self.RESERVE_CARDS - 1): 353 self.s.talon.dealRow(rows=self.s.reserves[:2], frames=0) 354 self.s.talon.dealRow(rows=self.s.reserves, frames=0) 355 self.startDealSample() 356 self.s.talon.dealRow() 357 self.s.talon.dealCards() 358 359 shallHighlightMatch = Game._shallHighlightMatch_SS 360 361 362class BackbonePlus(Backbone): 363 def createGame(self): 364 Backbone.createGame(self, rows=10) 365 366 367class SmallBackbone(Backbone): 368 RESERVE_CARDS = 9 369 370 371# ************************************************************************ 372# * Big Braid 373# ************************************************************************ 374 375class BigBraid(Braid): 376 Foundation_Classes = [Braid_Foundation, Braid_Foundation, Braid_Foundation] 377 378 379# ************************************************************************ 380# * Casket 381# ************************************************************************ 382 383class Casket_Hint(CautiousDefaultHint): 384 def computeHints(self): 385 CautiousDefaultHint.computeHints(self) 386 if self.hints: 387 return 388 if not self.game.s.waste.cards: 389 return 390 r = self.game.s.waste.cards[-1].rank 391 if 0 <= r <= 3: 392 to_stack = self.game.s.reserves[0] 393 elif 4 <= r <= 7: 394 to_stack = self.game.s.reserves[1] 395 else: 396 to_stack = self.game.s.reserves[2] 397 self.addHint(5000, 1, self.game.s.waste, to_stack) 398 399 400class JewelsStack(OpenStack): 401 def canFlipCard(self): 402 return False 403 404 405class Casket_RowStack(SS_RowStack): 406 407 getBottomImage = Stack._getReserveBottomImage 408 409 def acceptsCards(self, from_stack, cards): 410 if not SS_RowStack.acceptsCards(self, from_stack, cards): 411 return False 412 if not self.cards: 413 # don't accepts from lid 414 return from_stack not in self.game.s.lid 415 return True 416 417 418class Casket_Reserve(ReserveStack): 419 def acceptsCards(self, from_stack, cards): 420 if not ReserveStack.acceptsCards(self, from_stack, cards): 421 return False 422 return from_stack is self.game.s.waste 423 424 425class Casket(Game): 426 Hint_Class = Casket_Hint 427 428 def createGame(self): 429 # create layout 430 l, s = Layout(self), self.s 431 432 # set window 433 self.setSize(l.XM+10*l.XS, l.YM+4.5*l.YS) 434 435 # register extra stack variables 436 s.addattr(jewels=None) 437 s.addattr(lid=[]) 438 439 # create stacks 440 # Lid 441 x0, y0 = l.XM+2.5*l.XS, l.YM 442 for xx, yy in ((0, 0.5), 443 (1, 0.25), 444 (2, 0), 445 (3, 0.25), 446 (4, 0.5), 447 ): 448 x, y = x0+xx*l.XS, y0+yy*l.YS 449 s.lid.append(BasicRowStack(x, y, self, max_accept=0)) 450 451 # Casket 452 x0, y0 = l.XM+3*l.XS, l.YM+1.5*l.YS 453 for xx, yy in ((0, 0), (3, 0), 454 (0, 1), (3, 1), 455 (0, 2), (1, 2), (2, 2), (3, 2), 456 ): 457 x, y = x0+xx*l.XS, y0+yy*l.YS 458 stack = Casket_RowStack(x, y, self, max_move=1) 459 stack.CARD_YOFFSET = 0 460 s.rows.append(stack) 461 462 # Reserves 463 x, y = l.XM, l.YM+1.5*l.YS 464 for i in range(3): 465 stack = Casket_Reserve(x, y, self, max_cards=UNLIMITED_CARDS) 466 l.createText(stack, "ne") 467 s.reserves.append(stack) 468 y += l.YS 469 470 # Foundations 471 x = l.XM+8*l.XS 472 for i in range(2): 473 y = l.YM 474 for j in range(4): 475 s.foundations.append(SS_FoundationStack(x, y, self, suit=j)) 476 y += l.YS 477 x += l.XS 478 479 # Jewels 480 x, y = l.XM+4.5*l.XS, l.YM+2*l.YS 481 s.jewels = JewelsStack(x, y, self) 482 l.createText(s.jewels, "s") 483 484 # waste & talon 485 x, y = l.XM, l.YM 486 s.talon = WasteTalonStack(x, y, self, max_rounds=1) 487 l.createText(s.talon, "s") 488 x += l.XS 489 s.waste = WasteStack(x, y, self, max_cards=1) 490 491 # define stack-groups 492 self.sg.talonstacks = [s.talon] + [s.waste] 493 self.sg.openstacks = s.foundations + s.rows + s.reserves 494 self.sg.dropstacks = s.lid + s.rows + [s.waste] + s.reserves 495 496 def startGame(self): 497 for i in range(13): 498 self.s.talon.dealRow(rows=[self.s.jewels], frames=0, flip=0) 499 self.startDealSample() 500 self.s.talon.dealToStacksOrFoundations(stacks=self.s.lid) 501 self.s.talon.dealToStacksOrFoundations(stacks=self.s.rows) 502 self.s.talon.dealCards() 503 504 def fillStack(self, stack): 505 if not stack.cards and stack in self.s.lid: 506 if self.s.jewels.cards: 507 old_state = self.enterState(self.S_FILL) 508 self.s.jewels.flipMove() 509 self.s.jewels.moveMove(1, stack) 510 self.leaveState(old_state) 511 512 shallHighlightMatch = Game._shallHighlightMatch_SS 513 514 515# ************************************************************************ 516# * Well 517# ************************************************************************ 518 519class Well_TalonStack(DealRowRedealTalonStack): 520 521 def canDealCards(self): 522 return DealRowRedealTalonStack.canDealCards( 523 self, rows=self.game.s.wastes) 524 525 def dealCards(self, sound=False): 526 num_cards = 0 527 if sound and self.game.app.opt.animations: 528 self.game.startDealSample() 529 if not self.cards: 530 # move all cards to talon 531 num_cards = self._redeal(rows=self.game.s.wastes, frames=3) 532 self.game.nextRoundMove(self) 533 wastes = self.game.s.wastes[:(6-self.round)] 534 num_cards += self.dealRowAvail(rows=wastes, frames=4, sound=False) 535 if sound: 536 self.game.stopSamples() 537 return num_cards 538 539 540class Well(Game): 541 Hint_Class = CautiousDefaultHint 542 543 def createGame(self): 544 # create layout 545 l, s = Layout(self), self.s 546 547 # set window 548 self.setSize(l.XM+6*l.XS, l.YM+6*l.YS+l.TEXT_HEIGHT) 549 550 # register extra stack variables 551 s.addattr(wastes=[]) 552 553 # foundations 554 suit = 0 555 x0, y0 = l.XM+1.5*l.XS, l.YM+1.5*l.YS+l.TEXT_HEIGHT 556 for xx, yy in ((3, 0), 557 (0, 3), 558 (3, 3), 559 (0, 0)): 560 x, y = x0+xx*l.XS, y0+yy*l.YS 561 s.foundations.append(SS_FoundationStack(x, y, self, suit=suit, 562 base_rank=KING, mod=13, max_cards=26, 563 dir=-1, max_move=0)) 564 suit += 1 565 566 # rows 567 x0, y0 = l.XM+l.XS, l.YM+l.YS+l.TEXT_HEIGHT 568 for xx, yy in ((0, 2), 569 (2, 0), 570 (4, 2), 571 (2, 4)): 572 x, y = x0+xx*l.XS, y0+yy*l.YS 573 stack = SS_RowStack(x, y, self, dir=1, mod=13, max_move=1) 574 stack.getBottomImage = stack._getReserveBottomImage 575 stack.CARD_YOFFSET = 0 576 s.rows.append(stack) 577 578 # left stack 579 x, y = l.XM, l.YM+l.YS+l.TEXT_HEIGHT 580 stack = SS_RowStack( 581 x, y, self, base_rank=ACE, dir=1, mod=13, max_move=1) 582 stack.getBottomImage = stack._getReserveBottomImage 583 stack.CARD_YOFFSET = 0 584 s.rows.append(stack) 585 586 # reserves 587 x0, y0 = l.XM+2*l.XS, l.YM+2*l.YS+l.TEXT_HEIGHT 588 for xx, yy, anchor in ((0, 1, 'e'), 589 (1, 0, 's'), 590 (2, 1, 'w'), 591 (1, 2, 'n')): 592 x, y = x0+xx*l.XS, y0+yy*l.YS 593 stack = OpenStack(x, y, self) 594 l.createText(stack, anchor) 595 s.reserves.append(stack) 596 597 # wastes 598 x, y = l.XM+l.XS, l.YM 599 for i in range(5): 600 stack = WasteStack(x, y, self) 601 l.createText(stack, 's', text_format='%D') 602 s.wastes.append(stack) 603 x += l.XS 604 605 # talon 606 x, y = l.XM, l.YM 607 s.talon = Well_TalonStack(x, y, self, max_rounds=5) 608 l.createText(s.talon, "s") 609 610 # define stack-groups 611 self.sg.talonstacks = [s.talon] + s.wastes 612 self.sg.openstacks = s.foundations + s.rows 613 self.sg.dropstacks = s.rows + s.wastes + s.reserves 614 615 def startGame(self): 616 for i in range(10): 617 self.s.talon.dealRow(rows=self.s.reserves, frames=0) 618 self.startDealSample() 619 self.s.talon.dealRow(rows=self.s.rows[:4]) 620 self.s.talon.dealCards() 621 622 def fillStack(self, stack): 623 if not stack.cards and stack in self.s.rows[:4]: 624 indx = list(self.s.rows).index(stack) 625 r = self.s.reserves[indx] 626 if r.cards: 627 old_state = self.enterState(self.S_FILL) 628 r.moveMove(1, stack) 629 self.leaveState(old_state) 630 631 shallHighlightMatch = Game._shallHighlightMatch_SSW 632 633 634# register the game 635registerGame(GameInfo(12, Braid, "Braid", 636 GI.GT_NAPOLEON, 2, 2, GI.SL_BALANCED, 637 altnames=("Der Zopf", "Plait", "Pigtail"))) 638registerGame(GameInfo(175, LongBraid, "Long Braid", 639 GI.GT_NAPOLEON, 2, 2, GI.SL_BALANCED, 640 altnames=("Der lange Zopf",))) 641registerGame(GameInfo(358, Fort, "Fort", 642 GI.GT_NAPOLEON, 2, 2, GI.SL_BALANCED)) 643registerGame(GameInfo(376, Backbone, "Backbone", 644 GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED)) 645registerGame(GameInfo(377, BackbonePlus, "Backbone +", 646 GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED)) 647registerGame(GameInfo(510, BigBraid, "Big Braid", 648 GI.GT_NAPOLEON | GI.GT_ORIGINAL, 3, 2, GI.SL_BALANCED)) 649registerGame(GameInfo(694, Casket, "Casket", 650 GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED)) 651registerGame(GameInfo(717, Well, "Well", 652 GI.GT_2DECK_TYPE, 2, 4, GI.SL_BALANCED)) 653registerGame(GameInfo(824, SmallBackbone, "Small Backbone", 654 GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED)) 655