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 pysollib.game 25from pysollib.game import Game 26from pysollib.gamedb import GI, GameInfo, registerGame 27from pysollib.games.pileon import FourByFour_Hint 28from pysollib.hint import AbstractHint, CautiousDefaultHint, DefaultHint 29from pysollib.hint import BlackHoleSolverWrapper 30from pysollib.layout import Layout 31from pysollib.mfxutil import kwdefault 32from pysollib.mygettext import _ 33from pysollib.pysoltk import MfxCanvasText 34from pysollib.stack import \ 35 AbstractFoundationStack, \ 36 BasicRowStack, \ 37 DealRowTalonStack, \ 38 InitialDealTalonStack, \ 39 OpenStack, \ 40 RK_FoundationStack, \ 41 RK_RowStack, \ 42 ReserveStack, \ 43 SS_FoundationStack, \ 44 SS_RowStack, \ 45 Stack, \ 46 StackWrapper, \ 47 TalonStack, \ 48 UD_RK_RowStack, \ 49 WasteStack, \ 50 WasteTalonStack, \ 51 isSameSuitSequence 52from pysollib.util import ACE, ANY_RANK, ANY_SUIT, DIAMOND, KING, NO_RANK,\ 53 RANKS, SUITS, \ 54 UNLIMITED_REDEALS 55 56 57class Golf_Hint(AbstractHint): 58 # FIXME: this is very simple 59 60 def computeHints(self): 61 game = self.game 62 # for each stack 63 for r in game.sg.dropstacks: 64 # try if we can drop a card to the Waste 65 w, ncards = r.canDropCards(game.s.foundations) 66 if not w: 67 continue 68 # this assertion must hold for Golf 69 assert ncards == 1 70 # clone the Waste (including the card that will be dropped) to 71 # form our new foundations 72 ww = (self.ClonedStack(w, stackcards=w.cards+[r.cards[-1]]), ) 73 # now search for a stack that would benefit from this card 74 score, color = 10000 + r.id, None 75 for t in game.sg.dropstacks: 76 if not t.cards: 77 continue 78 if t is r: 79 t = self.ClonedStack(r, stackcards=r.cards[:-1]) 80 if t.canFlipCard(): 81 score = score + 100 82 elif t.canDropCards(ww)[0]: 83 score = score + 100 84 # add hint 85 self.addHint(score, ncards, r, w, color) 86 87 88# ************************************************************************ 89# * 90# ************************************************************************ 91 92class Golf_Talon(WasteTalonStack): 93 def canDealCards(self): 94 if not WasteTalonStack.canDealCards(self): 95 return False 96 return not self.game.isGameWon() 97 98 99class Golf_Waste(WasteStack): 100 def __init__(self, x, y, game, **cap): 101 kwdefault(cap, max_move=0, max_accept=1) 102 WasteStack.__init__(self, x, y, game, **cap) 103 104 def acceptsCards(self, from_stack, cards): 105 if from_stack is self.game.s.talon: 106 return True 107 if not WasteStack.acceptsCards(self, from_stack, cards): 108 return False 109 # check cards 110 r1, r2 = self.cards[-1].rank, cards[0].rank 111 if self.game.getStrictness() == 1: 112 # nothing on a King 113 if r1 == KING: 114 return False 115 return (r1 + 1) % self.cap.mod == r2 or (r2 + 1) % self.cap.mod == r1 116 117 def getHelp(self): 118 return _('Waste. Build up or down regardless of suit.') 119 120 121class Golf_RowStack(BasicRowStack): 122 def clickHandler(self, event): 123 return self.doubleclickHandler(event) 124 125 def getHelp(self): 126 return _('Tableau. No building.') 127 128 129# ************************************************************************ 130# * Golf 131# ************************************************************************ 132 133class Golf(Game): 134 Solver_Class = BlackHoleSolverWrapper(preset='golf', base_rank=0, 135 queens_on_kings=True) 136 Waste_Class = Golf_Waste 137 Hint_Class = Golf_Hint 138 139 # 140 # game layout 141 # 142 143 def createGame(self, columns=7): 144 # create layout 145 layout, s = Layout(self), self.s 146 147 # set window 148 playcards = 5 149 w1, w2 = (columns + 1) * layout.XS + layout.XM, 2 * layout.XS 150 151 totalcards = 52 * self.gameinfo.decks 152 if w2 + totalcards * layout.XOFFSET > w1: 153 layout.XOFFSET = int((w1 - w2) / totalcards) 154 self.setSize(w1, layout.YM + 3 * layout.YS + 155 (playcards - 1) * layout.YOFFSET + layout.TEXT_HEIGHT) 156 157 # create stacks 158 x, y = layout.XM + layout.XS // 2, layout.YM 159 for i in range(columns): 160 s.rows.append(Golf_RowStack(x, y, self)) 161 x = x + layout.XS 162 x, y = layout.XM, self.height - layout.YS 163 s.talon = Golf_Talon(x, y, self, max_rounds=1) 164 layout.createText(s.talon, "n") 165 x = x + layout.XS 166 s.waste = self.Waste_Class(x, y, self) 167 s.waste.CARD_XOFFSET = layout.XOFFSET 168 layout.createText(s.waste, "n") 169 # the Waste is also our only Foundation in this game 170 s.foundations.append(s.waste) 171 172 # define stack-groups (non default) 173 self.sg.openstacks = [s.waste] 174 self.sg.talonstacks = [s.talon] 175 self.sg.dropstacks = s.rows 176 177 # 178 # game overrides 179 # 180 181 def startGame(self, num_rows=5): 182 self._startDealNumRows(num_rows - 1) 183 self.s.talon.dealRow() 184 self.s.talon.dealCards() # deal first card to WasteStack 185 186 def isGameWon(self): 187 for r in self.s.rows: 188 if r.cards: 189 return False 190 return True 191 192 shallHighlightMatch = Game._shallHighlightMatch_RK 193 194 def getHighlightPilesStacks(self): 195 return () 196 197 def getAutoStacks(self, event=None): 198 if event is None: 199 # disable auto drop - this would ruin the whole gameplay 200 return (self.sg.dropstacks, (), ()) 201 else: 202 # rightclickHandler 203 return (self.sg.dropstacks, self.sg.dropstacks, ()) 204 205 206# ************************************************************************ 207# * Double Golf 208# ************************************************************************ 209 210class DoubleGolf(Golf): 211 212 def createGame(self): 213 Golf.createGame(self, 9) 214 215 def startGame(self): 216 Golf.startGame(self, 7) 217 218 219# ************************************************************************ 220# * 221# ************************************************************************ 222 223class DeadKingGolf(Golf): 224 Solver_Class = BlackHoleSolverWrapper(preset='golf', base_rank=0) 225 226 def getStrictness(self): 227 return 1 228 229 def shallHighlightMatch(self, stack1, card1, stack2, card2): 230 if card1.rank == KING: 231 return False 232 return Golf.shallHighlightMatch(self, stack1, card1, stack2, card2) 233 234 235class RelaxedGolf(Golf): 236 Solver_Class = BlackHoleSolverWrapper(preset='golf', base_rank=0, 237 wrap_ranks=True) 238 Waste_Class = StackWrapper(Golf_Waste, mod=13) 239 240 shallHighlightMatch = Game._shallHighlightMatch_RKW 241 242 243# ************************************************************************ 244# * Elevator - Relaxed Golf in a Pyramid layout 245# ************************************************************************ 246 247class Elevator_RowStack(Golf_RowStack): 248 STEP = (1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6) 249 250 def basicIsBlocked(self): 251 r, step = self.game.s.rows, self.STEP 252 i, n, mylen = self.id, 1, len(step) 253 while i < mylen: 254 i = i + step[i] 255 n = n + 1 256 for j in range(i, i+n): 257 if r[j].cards: 258 return True 259 return False 260 261 262class Elevator(RelaxedGolf): 263 264 # 265 # game layout 266 # 267 268 def createGame(self): 269 # create layout 270 layout, s = Layout(self), self.s 271 272 # set window 273 self.setSize(9*layout.XS+layout.XM, 4*layout.YS+layout.YM) 274 275 # create stacks 276 for i in range(7): 277 x = layout.XM + (8-i) * layout.XS // 2 278 y = layout.YM + i * layout.YS // 2 279 for j in range(i+1): 280 s.rows.append(Elevator_RowStack(x, y, self)) 281 x = x + layout.XS 282 x, y = layout.XM, layout.YM 283 s.talon = Golf_Talon(x, y, self, max_rounds=1) 284 layout.createText(s.talon, "s") 285 x = x + layout.XS 286 s.waste = self.Waste_Class(x, y, self) 287 layout.createText(s.waste, "s") 288 # the Waste is also our only Foundation in this game 289 s.foundations.append(s.waste) 290 291 # define stack-groups (non default) 292 self.sg.openstacks = [s.waste] 293 self.sg.talonstacks = [s.talon] 294 self.sg.dropstacks = s.rows 295 296 # 297 # game overrides 298 # 299 300 def startGame(self): 301 self.startDealSample() 302 self.s.talon.dealRow(rows=self.s.rows[:21], flip=0) 303 self.s.talon.dealRow(rows=self.s.rows[21:]) 304 self.s.talon.dealCards() # deal first card to WasteStack 305 306 307class Escalator(pysollib.game.StartDealRowAndCards, Elevator): 308 pass 309 310 311# ************************************************************************ 312# * Black Hole 313# ************************************************************************ 314 315class BlackHole_Foundation(AbstractFoundationStack): 316 def acceptsCards(self, from_stack, cards): 317 if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): 318 return False 319 # check the rank 320 if self.cards: 321 r1, r2 = self.cards[-1].rank, cards[0].rank 322 return (r1 + 1) % self.cap.mod == r2 or \ 323 (r2 + 1) % self.cap.mod == r1 324 return True 325 326 def getHelp(self): 327 return _('Foundation. Build up or down regardless of suit.') 328 329 330class BlackHole_RowStack(ReserveStack): 331 def clickHandler(self, event): 332 return self.doubleclickHandler(event) 333 334 def getHelp(self): 335 return _('Tableau. No building.') 336 337 338class BlackHole(Game): 339 RowStack_Class = StackWrapper( 340 BlackHole_RowStack, max_accept=0, max_cards=3) 341 Hint_Class = Golf_Hint 342 Solver_Class = BlackHoleSolverWrapper(preset='black_hole') 343 344 # 345 # game layout 346 # 347 348 def createGame(self, playcards=5): 349 # create layout 350 layout, s = Layout(self), self.s 351 352 # set window 353 w = max(2*layout.XS, layout.XS+(playcards-1)*layout.XOFFSET) 354 self.setSize(layout.XM + 5*w, layout.YM + 4*layout.YS) 355 356 # create stacks 357 y = layout.YM 358 for i in range(5): 359 x = layout.XM + i*w 360 s.rows.append(self.RowStack_Class(x, y, self)) 361 for i in range(2): 362 y = y + layout.YS 363 for j in (0, 1, 3, 4): 364 x = layout.XM + j*w 365 s.rows.append(self.RowStack_Class(x, y, self)) 366 y = y + layout.YS 367 for i in range(4): 368 x = layout.XM + i*w 369 s.rows.append(self.RowStack_Class(x, y, self)) 370 for r in s.rows: 371 r.CARD_XOFFSET = layout.XOFFSET 372 r.CARD_YOFFSET = 0 373 x, y = layout.XM + 2*w, layout.YM + 3*layout.YS//2 374 s.foundations.append(BlackHole_Foundation(x, y, self, suit=ANY_SUIT, 375 dir=0, mod=13, max_move=0, max_cards=52)) 376 layout.createText(s.foundations[0], "s") 377 x, y = layout.XM + 4*w, self.height - layout.YS 378 s.talon = InitialDealTalonStack(x, y, self) 379 380 # define stack-groups 381 layout.defaultStackGroups() 382 383 # 384 # game overrides 385 # 386 387 def _shuffleHook(self, cards): 388 # move Ace to bottom of the Talon (i.e. last cards to be dealt) 389 return self._shuffleHookMoveToBottom( 390 cards, lambda c: (c.id == 13, c.suit), 1) 391 392 def startGame(self): 393 self._startDealNumRows(2) 394 self.s.talon.dealRow() 395 self.s.talon.dealRow(rows=self.s.foundations) 396 397 def getAutoStacks(self, event=None): 398 if event is None: 399 # disable auto drop - this would ruin the whole gameplay 400 return ((), (), self.sg.dropstacks) 401 else: 402 # rightclickHandler 403 return ((), self.sg.dropstacks, self.sg.dropstacks) 404 405 406# ************************************************************************ 407# * Four Leaf Clovers 408# ************************************************************************ 409 410class FourLeafClovers_Foundation(AbstractFoundationStack): 411 def acceptsCards(self, from_stack, cards): 412 if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): 413 return False 414 # check the rank 415 if self.cards: 416 r1, r2 = self.cards[-1].rank, cards[0].rank 417 return (r1 + 1) % self.cap.mod == r2 418 return True 419 420 def getHelp(self): 421 return _('Foundation. Build up regardless of suit.') 422 423 424class FourLeafClovers(Game): 425 426 Hint_Class = CautiousDefaultHint 427 428 # 429 # game layout 430 # 431 432 def createGame(self): 433 # create layout 434 layout, s = Layout(self), self.s 435 436 # set window 437 h = layout.YS + 6*layout.YOFFSET 438 self.setSize(layout.XM + 7*layout.XS, layout.YM + 2*h) 439 440 # create stacks 441 y = layout.YM 442 for i in range(7): 443 x = layout.XM + i*layout.XS 444 s.rows.append( 445 UD_RK_RowStack(x, y, self, mod=13, base_rank=NO_RANK)) 446 y = layout.YM+h 447 for i in range(6): 448 x = layout.XM + i*layout.XS 449 s.rows.append( 450 UD_RK_RowStack(x, y, self, mod=13, base_rank=NO_RANK)) 451 stack = FourLeafClovers_Foundation( 452 layout.XM+6*layout.XS, self.height-layout.YS, self, 453 suit=ANY_SUIT, dir=0, mod=13, 454 max_move=0, max_cards=52) 455 s.foundations.append(stack) 456 layout.createText(stack, 'n') 457 x, y = layout.XM + 7*layout.XS, self.height - layout.YS 458 s.talon = InitialDealTalonStack(x, y, self) 459 460 # define stack-groups 461 layout.defaultStackGroups() 462 463 def startGame(self): 464 self._startDealNumRowsAndDealSingleRow(3) 465 466 shallHighlightMatch = Game._shallHighlightMatch_RKW 467 468 469# ************************************************************************ 470# * All in a Row 471# ************************************************************************ 472 473class AllInARow(BlackHole): 474 475 Solver_Class = BlackHoleSolverWrapper(preset='all_in_a_row') 476 477 def createGame(self): 478 # create layout 479 layout, s = Layout(self), self.s 480 481 # set window 482 h = layout.YM+layout.YS+4*layout.YOFFSET 483 self.setSize(layout.XM+7*layout.XS, 3*layout.YM+2*h+layout.YS) 484 485 # create stacks 486 x, y = layout.XM, layout.YM 487 for i in range(7): 488 s.rows.append(self.RowStack_Class(x, y, self)) 489 x += layout.XS 490 x, y = layout.XM, layout.YM+h 491 for i in range(6): 492 s.rows.append(self.RowStack_Class(x, y, self)) 493 x += layout.XS 494 for r in s.rows: 495 r.CARD_XOFFSET, r.CARD_YOFFSET = 0, layout.YOFFSET 496 497 x, y = layout.XM, self.height-layout.YS 498 stack = BlackHole_Foundation( 499 x, y, self, ANY_SUIT, dir=0, mod=13, max_move=0, max_cards=52, 500 base_rank=ANY_RANK) 501 s.foundations.append(stack) 502 stack.CARD_XOFFSET, stack.CARD_YOFFSET = (self.width-layout.XS)//51, 0 503 layout.createText(stack, 'n') 504 x = self.width-layout.XS 505 s.talon = InitialDealTalonStack(x, y, self) 506 507 # define stack-groups 508 layout.defaultStackGroups() 509 510 def startGame(self): 511 self._startDealNumRowsAndDealSingleRow(3) 512 513 514# ************************************************************************ 515# * Robert 516# * Wasatch 517# ************************************************************************ 518 519class Robert(Game): 520 521 def createGame(self, max_rounds=3, num_deal=1): 522 layout, s = Layout(self), self.s 523 self.setSize(layout.XM+4*layout.XS, layout.YM+2*layout.YS) 524 x, y = layout.XM+3*layout.XS//2, layout.YM 525 stack = BlackHole_Foundation(x, y, self, ANY_SUIT, 526 dir=0, mod=13, max_move=0, max_cards=52) 527 s.foundations.append(stack) 528 layout.createText(stack, 'ne') 529 x, y = layout.XM+layout.XS, layout.YM+layout.YS 530 s.talon = WasteTalonStack(x, y, self, 531 max_rounds=max_rounds, num_deal=num_deal) 532 layout.createText(s.talon, 'nw') 533 if max_rounds > 0: 534 layout.createRoundText(self.s.talon, 'se', dx=layout.XS) 535 x += layout.XS 536 s.waste = WasteStack(x, y, self) 537 layout.createText(s.waste, 'ne') 538 539 # define stack-groups 540 layout.defaultStackGroups() 541 542 def startGame(self): 543 self.startDealSample() 544 self.s.talon.dealRow(rows=self.s.foundations) 545 self.s.talon.dealCards() 546 547 548class Wasatch(Robert): 549 550 def createGame(self): 551 Robert.createGame(self, max_rounds=UNLIMITED_REDEALS, num_deal=3) 552 553 def startGame(self): 554 Robert.startGame(self) 555 556# ************************************************************************ 557# * Uintah 558# ************************************************************************ 559 560 561class Uintah_Foundation(AbstractFoundationStack): 562 def acceptsCards(self, from_stack, cards): 563 if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): 564 return False 565 if (self.cards[-1].color != cards[0].color): 566 return False 567 # check the rank 568 if self.cards: 569 r1, r2 = self.cards[-1].rank, cards[0].rank 570 return (r1 + 1) % self.cap.mod == r2 or \ 571 (r2 + 1) % self.cap.mod == r1 572 return True 573 574 def getHelp(self): 575 return _('Foundation. Build up or down by same color.') 576 577 578class Uintah(Game): 579 580 def createGame(self): 581 layout, s = Layout(self), self.s 582 self.setSize(layout.XM + 4 * layout.XS, layout.YM + 2 * layout.YS) 583 x, y = layout.XM, layout.YM 584 for i in range(4): 585 s.foundations.append(Uintah_Foundation(x, y, self, 586 suit=ANY_SUIT, dir=0, mod=13, 587 max_cards=52, max_move=0)) 588 x += layout.XS 589 x, y = layout.XM + layout.XS, layout.YM + layout.YS 590 s.talon = WasteTalonStack(x, y, self, 591 max_rounds=UNLIMITED_REDEALS, num_deal=3) 592 layout.createText(s.talon, 'nw') 593 x += layout.XS 594 s.waste = WasteStack(x, y, self) 595 layout.createText(s.waste, 'ne') 596 597 # define stack-groups 598 layout.defaultStackGroups() 599 600 def _shuffleHook(self, cards): 601 suits = [] 602 top_cards = [] 603 for c in cards[:]: 604 if c.suit not in suits: 605 suits.append(c.suit) 606 top_cards.append(c) 607 cards.remove(c) 608 if len(suits) == 4: 609 break 610 top_cards.sort(key=lambda x: -x.suit) # sort by suit 611 return cards + top_cards 612 613 def startGame(self): 614 self.startDealSample() 615 self.s.talon.dealRow(rows=self.s.foundations) 616 self.s.talon.dealCards() 617 618 619# ************************************************************************ 620# * Diamond Mine 621# ************************************************************************ 622 623class DiamondMine_RowStack(RK_RowStack): 624 def acceptsCards(self, from_stack, cards): 625 if not RK_RowStack.acceptsCards(self, from_stack, cards): 626 return False 627 if cards[0].suit == DIAMOND: 628 return False 629 if self.cards: 630 return self.cards[-1].suit != DIAMOND 631 return True 632 633 634class DiamondMine(Game): 635 636 def createGame(self): 637 layout, s = Layout(self), self.s 638 self.setSize( 639 layout.XM+13*layout.XS, 640 layout.YM+2*layout.YS+15*layout.YOFFSET) 641 642 x, y = layout.XM+6*layout.XS, layout.YM 643 s.foundations.append(SS_FoundationStack(x, y, self, 644 base_rank=ANY_RANK, suit=DIAMOND, mod=13)) 645 x, y = layout.XM, layout.YM+layout.YS 646 for i in range(13): 647 s.rows.append(DiamondMine_RowStack(x, y, self)) 648 x += layout.XS 649 s.talon = InitialDealTalonStack(layout.XM, self.height-layout.YS, self) 650 651 layout.defaultAll() 652 653 def startGame(self): 654 for i in range(3): 655 self.s.talon.dealRow(flip=0, frames=0) 656 self._startAndDealRow() 657 658 def isGameWon(self): 659 if len(self.s.foundations[0].cards) != 13: 660 return False 661 for s in self.s.rows: 662 if len(s.cards) == 0: 663 continue 664 if len(s.cards) != 13: 665 return False 666 if not isSameSuitSequence(s.cards): 667 return False 668 return True 669 670 shallHighlightMatch = Game._shallHighlightMatch_RK 671 672 673# ************************************************************************ 674# * Dolphin 675# ************************************************************************ 676 677class Dolphin(Game): 678 679 def createGame(self, rows=8, reserves=4, playcards=6): 680 layout, s = Layout(self), self.s 681 self.setSize( 682 layout.XM+rows*layout.XS, 683 layout.YM+3*layout.YS+playcards*layout.YOFFSET) 684 685 dx = (self.width-layout.XM-(reserves+1)*layout.XS)//3 686 x, y = layout.XM+dx, layout.YM 687 for i in range(reserves): 688 s.reserves.append(ReserveStack(x, y, self)) 689 x += layout.XS 690 x += dx 691 max_cards = 52*self.gameinfo.decks 692 s.foundations.append(RK_FoundationStack(x, y, self, 693 base_rank=ANY_RANK, mod=13, max_cards=max_cards)) 694 layout.createText(s.foundations[0], 'ne') 695 x, y = layout.XM, layout.YM+layout.YS 696 for i in range(rows): 697 s.rows.append(BasicRowStack(x, y, self)) 698 x += layout.XS 699 s.talon = InitialDealTalonStack(layout.XM, self.height-layout.YS, self) 700 701 layout.defaultAll() 702 703 def startGame(self): 704 self._startDealNumRows(5) 705 self.s.talon.dealRow() 706 self.s.talon.dealRowAvail() 707 708 709class DoubleDolphin(Dolphin): 710 711 def createGame(self): 712 Dolphin.createGame(self, rows=10, reserves=5, playcards=10) 713 714 def startGame(self): 715 self._startDealNumRows(9) 716 self.s.talon.dealRow() 717 self.s.talon.dealRowAvail() 718 719 720# ************************************************************************ 721# * Waterfall 722# ************************************************************************ 723 724class Waterfall_Foundation(AbstractFoundationStack): 725 def acceptsCards(self, from_stack, cards): 726 if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): 727 return False 728 c1 = cards[0] 729 if not self.cards: 730 return c1.rank == ACE and c1.suit == 0 731 c2 = self.cards[-1] 732 if c2.rank == KING: 733 suit = (c2.suit+1) % 4 734 rank = ACE 735 else: 736 suit = c2.suit 737 rank = c2.rank+1 738 return c1.suit == suit and c1.rank == rank 739 740 741class Waterfall(Game): 742 743 def createGame(self): 744 rows = 8 745 layout, s = Layout(self), self.s 746 self.setSize( 747 layout.XM+rows*layout.XS, layout.YM+2*layout.YS+20*layout.YOFFSET) 748 749 x, y = layout.XM, layout.YM 750 for i in range(rows): 751 s.rows.append(RK_RowStack(x, y, self)) 752 x += layout.XS 753 x, y = layout.XM+(rows-1)*layout.XS//2, self.height-layout.YS 754 s.foundations.append(Waterfall_Foundation(x, y, self, suit=ANY_SUIT, 755 max_cards=104)) 756 stack = s.foundations[0] 757 tx, ty, ta, tf = layout.getTextAttr(stack, 'se') 758 font = self.app.getFont('canvas_default') 759 stack.texts.misc = MfxCanvasText(self.canvas, tx, ty, 760 anchor=ta, font=font) 761 x, y = self.width-layout.XS, self.height-layout.YS 762 s.talon = DealRowTalonStack(x, y, self) 763 layout.createText(s.talon, 'sw') 764 765 layout.defaultStackGroups() 766 767 def startGame(self): 768 self._startDealNumRowsAndDealSingleRow(3) 769 770 def updateText(self): 771 if self.preview > 1: 772 return 773 f = self.s.foundations[0] 774 if len(f.cards) == 104: 775 t = '' 776 elif len(f.cards) == 0: 777 t = SUITS[0] 778 else: 779 c = f.cards[-1] 780 if c.rank == KING: 781 suit = (c.suit+1) % 4 782 else: 783 suit = c.suit 784 t = SUITS[suit] 785 f.texts.misc.config(text=t) 786 787 shallHighlightMatch = Game._shallHighlightMatch_RK 788 789 790# ************************************************************************ 791# * Vague 792# * Thirty Two Cards 793# ************************************************************************ 794 795class Vague_RowStack(BasicRowStack): 796 clickHandler = BasicRowStack.doubleclickHandler 797 798 799class Vague(Game): 800 Foundation_Classes = [StackWrapper(SS_FoundationStack, 801 base_rank=ANY_RANK, mod=13)] 802 803 SEPARATE_FOUNDATIONS = True 804 SPREAD_FOUNDATION = False 805 806 def createGame(self, rows=3, columns=6): 807 layout, s = Layout(self), self.s 808 decks = self.gameinfo.decks 809 maxrows = max(columns, 2+decks*4) 810 self.setSize(layout.XM+maxrows*layout.XS, layout.YM+(rows+1)*layout.YS) 811 812 x, y = layout.XM, layout.YM 813 s.talon = TalonStack(x, y, self) 814 layout.createText(s.talon, 'ne') 815 816 x, y = layout.XM+2*layout.XS, layout.YM 817 for found in self.Foundation_Classes: 818 if self.SEPARATE_FOUNDATIONS: 819 for i in range(4): 820 s.foundations.append(found(x, y, self, suit=i)) 821 x += layout.XS 822 else: 823 s.foundations.append(found(x, y, self, suit=ANY_SUIT)) 824 if self.SPREAD_FOUNDATION: 825 w1, w2 = 6 * layout.XS + layout.XM, 2 * layout.XS 826 827 totalcards = self.gameinfo.ncards 828 if w2 + totalcards * layout.XOFFSET > w1: 829 layout.XOFFSET = int((w1 - w2) / totalcards) 830 s.foundations[0].CARD_XOFFSET = layout.XOFFSET 831 832 y = layout.YM+layout.YS 833 for i in range(rows): 834 x = layout.XM + (maxrows-columns)*layout.XS//2 835 for j in range(columns): 836 s.rows.append(Vague_RowStack(x, y, self)) 837 x += layout.XS 838 y += layout.YS 839 840 layout.defaultStackGroups() 841 842 def startGame(self): 843 self.startDealSample() 844 self.s.talon.dealRow() 845 self.s.talon.flipMove() 846 847 def fillStack(self, stack): 848 if stack in self.s.rows and not stack.cards: 849 if self.s.talon.cards: 850 old_state = self.enterState(self.S_FILL) 851 if not self.s.talon.cards[-1].face_up: 852 self.s.talon.flipMove() 853 self.s.talon.moveMove(1, stack) 854 self.leaveState(old_state) 855 856 def getAutoStacks(self, event=None): 857 if event is None: 858 # disable auto drop - this would ruin the whole gameplay 859 return ((), (), self.sg.dropstacks) 860 else: 861 # rightclickHandler 862 return ((), self.sg.dropstacks, self.sg.dropstacks) 863 864 865class ThirtyTwoCards(Vague): 866 Foundation_Classes = [ 867 SS_FoundationStack, 868 StackWrapper(SS_FoundationStack, base_rank=KING, dir=-1)] 869 870 def createGame(self): 871 Vague.createGame(self, rows=4, columns=8) 872 873 def startGame(self): 874 self._startAndDealRow() 875 876 877# ************************************************************************ 878# * Sticko 879# ************************************************************************ 880 881class Sticko_Foundation(AbstractFoundationStack): 882 def acceptsCards(self, from_stack, cards): 883 r1, r2 = self.cards[-1].rank, cards[0].rank 884 s1, s2 = self.cards[-1].suit, cards[0].suit 885 c1, c2 = self.cards[-1].color, cards[0].color 886 887 # Increase rank, same suit 888 if ((r2 == r1 + 1 or (r2 == ACE and r1 == KING) 889 or (r2 == 6 and r1 == ACE)) and s1 == s2): 890 return True 891 892 # Decrease rank, different suit but same color 893 if ((r1 == r2 + 1 or (r1 == ACE and r2 == KING) 894 or (r1 == 6 and r2 == ACE)) and s1 != s2 and c1 == c2): 895 return True 896 897 # Same rank, different color 898 if r1 == r2 and c1 != c2: 899 return True 900 901 return False 902 903 904class Sticko(Vague): 905 Foundation_Classes = [StackWrapper(Sticko_Foundation, 906 max_cards=32, )] 907 SEPARATE_FOUNDATIONS = False 908 SPREAD_FOUNDATION = True 909 910 def createGame(self): 911 Vague.createGame(self, rows=2, columns=8) 912 913 def startGame(self): 914 self._startAndDealRow() 915 self.s.talon.flipMove() 916 self.s.talon.moveMove(1, self.s.foundations[0]) 917 918 919# ************************************************************************ 920# * Devil's Solitaire 921# ************************************************************************ 922 923class DevilsSolitaire_Foundation(RK_FoundationStack): 924 def acceptsCards(self, from_stack, cards): 925 if not RK_FoundationStack.acceptsCards(self, from_stack, cards): 926 return False 927 if self.cards: 928 return True 929 if self.game.s.reserves[0].cards: 930 c = self.game.s.reserves[0].cards[-1] 931 return (c.rank+1) % 13 == cards[-1].rank 932 return True 933 934 935class DevilsSolitaire_WasteStack(WasteStack): 936 clickHandler = WasteStack.doubleclickHandler 937 938 939class DevilsSolitaire(Game): 940 941 def createGame(self): 942 layout, s = Layout(self), self.s 943 self.setSize( 944 layout.XM+9*layout.XS, 945 layout.YM+3*layout.YS+7*layout.YOFFSET+2*layout.TEXT_HEIGHT) 946 947 x, y = layout.XM+4*layout.XS, layout.YM 948 stack = DevilsSolitaire_Foundation( 949 x, y, self, suit=ANY_SUIT, base_rank=ANY_RANK, mod=13) 950 tx, ty, ta, tf = layout.getTextAttr(stack, 'nw') 951 font = self.app.getFont('canvas_default') 952 stack.texts.misc = MfxCanvasText(self.canvas, tx, ty, 953 anchor=ta, font=font) 954 s.foundations.append(stack) 955 956 x, y = self.width-layout.XS, layout.YM 957 stack = AbstractFoundationStack( 958 x, y, self, 959 suit=ANY_SUIT, max_move=0, max_cards=104, 960 max_accept=0, base_rank=ANY_RANK) 961 layout.createText(stack, 'nw') 962 s.foundations.append(stack) 963 964 x, y = layout.XM, layout.YM+layout.YS 965 for i in range(4): 966 s.rows.append(Vague_RowStack(x, y, self)) 967 x += layout.XS 968 x += layout.XS 969 for i in range(4): 970 s.rows.append(Vague_RowStack(x, y, self)) 971 x += layout.XS 972 973 x, y = layout.XM+4*layout.XS, layout.YM+layout.YS 974 stack = OpenStack(x, y, self) 975 stack.CARD_YOFFSET = layout.YOFFSET 976 s.reserves.append(stack) 977 978 x, y = layout.XM+4.5*layout.XS, self.height-layout.YS 979 s.talon = WasteTalonStack(x, y, self, max_rounds=3) 980 layout.createText(s.talon, 'n') 981 layout.createRoundText(s.talon, 'nnn') 982 x -= layout.XS 983 s.waste = DevilsSolitaire_WasteStack(x, y, self) 984 layout.createText(s.waste, 'n') 985 986 layout.defaultStackGroups() 987 988 def startGame(self): 989 for i in range(8): 990 self.s.talon.dealRow(rows=self.s.reserves, frames=0) 991 self.startDealSample() 992 self.s.talon.dealRow() 993 self.s.talon.dealCards() 994 995 def fillStack(self, stack): 996 old_state = self.enterState(self.S_FILL) 997 if stack in self.s.rows and not stack.cards: 998 if not self.s.waste.cards: 999 self.s.talon.dealCards() 1000 if self.s.waste.cards: 1001 self.s.waste.moveMove(1, stack) 1002 f0 = self.s.foundations[0] 1003 if len(f0.cards) == 12: 1004 self.moveMove(1, self.s.reserves[0], f0, frames=4) 1005 f1 = self.s.foundations[1] 1006 for i in range(13): 1007 self.moveMove(1, f0, f1, frames=4) 1008 self.leaveState(old_state) 1009 1010 def updateText(self): 1011 if self.preview > 1: 1012 return 1013 f = self.s.foundations[0] 1014 r = self.s.reserves[0] 1015 if not r.cards: 1016 t = '' 1017 else: 1018 c = r.cards[-1] 1019 t = RANKS[(c.rank+1) % 13] 1020 f.texts.misc.config(text=t) 1021 1022 1023# ************************************************************************ 1024# * Three Fir-trees 1025# ************************************************************************ 1026 1027class ThreeFirTrees_RowStack(Golf_RowStack): 1028 def __init__(self, x, y, game): 1029 Golf_RowStack.__init__(self, x, y, game, max_accept=0, max_cards=1) 1030 self.CARD_YOFFSET = 0 1031 self.blockmap = [] 1032 1033 def basicIsBlocked(self): 1034 for r in self.blockmap: 1035 if r.cards: 1036 return True 1037 return False 1038 1039 getBottomImage = Stack._getNoneBottomImage 1040 1041 1042class FirTree_GameMethods: 1043 def _createFirTree(self, layout, x0, y0): 1044 rows = [] 1045 # create stacks 1046 for i in range(11): 1047 x = x0 + ((i+1) % 2) * layout.XS // 2 1048 y = y0 + i * layout.YS // 4 1049 for j in range((i % 2) + 1): 1050 rows.append(ThreeFirTrees_RowStack(x, y, self)) 1051 x += layout.XS 1052 # compute blocking 1053 n = 0 1054 for i in range(10): 1055 if i % 2: 1056 rows[n].blockmap = [rows[n+2]] 1057 rows[n+1].blockmap = [rows[n+2]] 1058 n += 2 1059 else: 1060 rows[n].blockmap = [rows[n+1], rows[n+2]] 1061 n += 1 1062 return rows 1063 1064 1065class ThreeFirTrees(Golf, FirTree_GameMethods): 1066 Hint_Class = CautiousDefaultHint 1067 Waste_Class = Golf_Waste 1068 1069 def createGame(self): 1070 1071 layout, s = Layout(self), self.s 1072 self.setSize( 1073 layout.XM+max(7*layout.XS, 2*layout.XS+26*layout.XOFFSET), 1074 layout.YM+5*layout.YS) 1075 1076 x0, y0 = (self.width-7*layout.XS)//2, layout.YM 1077 for i in range(3): 1078 s.rows += self._createFirTree(layout, x0, y0) 1079 x0 += 2.5*layout.XS 1080 1081 x, y = layout.XM, self.height - layout.YS 1082 s.talon = Golf_Talon(x, y, self, max_rounds=1) 1083 layout.createText(s.talon, 'n') 1084 x += layout.XS 1085 s.waste = self.Waste_Class(x, y, self) 1086 s.waste.CARD_XOFFSET = layout.XOFFSET//4 1087 layout.createText(s.waste, 'n') 1088 # the Waste is also our only Foundation in this game 1089 s.foundations.append(s.waste) 1090 1091 # define stack-groups (non default) 1092 self.sg.openstacks = [s.waste] 1093 self.sg.talonstacks = [s.talon] 1094 self.sg.dropstacks = s.rows 1095 1096 def startGame(self): 1097 self.startDealSample() 1098 self.s.talon.dealRow(frames=4) 1099 self.s.talon.dealCards() 1100 1101 1102class RelaxedThreeFirTrees(ThreeFirTrees): 1103 Waste_Class = StackWrapper(Golf_Waste, mod=13) 1104 1105 1106# ************************************************************************ 1107# * Napoleon Takes Moscow 1108# * Napoleon Leaves Moscow 1109# ************************************************************************ 1110 1111class NapoleonTakesMoscow(Game, FirTree_GameMethods): 1112 RowStack_Class = StackWrapper(SS_RowStack, base_rank=KING, max_move=1) 1113 Hint_Class = CautiousDefaultHint 1114 1115 def createGame(self): 1116 1117 layout, s = Layout(self), self.s 1118 self.setSize( 1119 layout.XM+10*layout.XS, layout.YM+3*layout.YS+15*layout.YOFFSET) 1120 1121 x, y = layout.XM+layout.XS, layout.YM 1122 for i in range(8): 1123 s.foundations.append(SS_FoundationStack(x, y, self, suit=i//2)) 1124 x += layout.XS 1125 1126 x, y = layout.XM, layout.YM+layout.YS 1127 for i in range(2): 1128 for j in range(4): 1129 s.rows.append(self.RowStack_Class(x, y, self)) 1130 x += layout.XS 1131 x += 2*layout.XS 1132 1133 x, y = layout.XM+4*layout.XS, layout.YM+layout.YS 1134 s.reserves += self._createFirTree(layout, x, y) 1135 1136 x, y = layout.XM, self.height-layout.YS 1137 s.talon = WasteTalonStack(x, y, self, max_rounds=3) 1138 layout.createText(s.talon, 'n') 1139 layout.createRoundText(s.talon, 'nnn') 1140 x += layout.XS 1141 s.waste = WasteStack(x, y, self) 1142 layout.createText(s.waste, 'n') 1143 1144 # define stack-groups 1145 layout.defaultStackGroups() 1146 1147 def startGame(self): 1148 self.s.talon.dealRow(rows=self.s.reserves, frames=0) 1149 self._startDealNumRowsAndDealRowAndCards(3) 1150 1151 shallHighlightMatch = Game._shallHighlightMatch_SS 1152 1153 1154class NapoleonLeavesMoscow(NapoleonTakesMoscow): 1155 RowStack_Class = StackWrapper(SS_RowStack, base_rank=KING) 1156 Hint_Class = DefaultHint 1157 1158 def startGame(self): 1159 self.s.talon.dealRow(rows=self.s.reserves, frames=0) 1160 self._startDealNumRowsAndDealRowAndCards(4) 1161 1162 1163# ************************************************************************ 1164# * Flake 1165# * Flake (2 decks) 1166# ************************************************************************ 1167 1168 1169class Flake(Game): 1170 Hint_Class = FourByFour_Hint # CautiousDefaultHint 1171 1172 def createGame(self, rows=6, playcards=18): 1173 # create layout 1174 layout, s = Layout(self), self.s 1175 1176 # set window 1177 self.setSize( 1178 layout.XM + rows*layout.XS, 1179 layout.YM + 2*layout.YS + playcards*layout.XOFFSET) 1180 1181 # create stacks 1182 x, y, = layout.XM, layout.YM+layout.YS 1183 for i in range(rows): 1184 s.rows.append(UD_RK_RowStack(x, y, self, mod=13)) 1185 x += layout.XS 1186 1187 x, y = layout.XM + (rows-1)*layout.XS//2, layout.YM 1188 stack = BlackHole_Foundation(x, y, self, max_move=0, suit=ANY_SUIT, 1189 base_rank=ANY_RANK, dir=0, mod=13, 1190 max_cards=52*self.gameinfo.decks) 1191 s.foundations.append(stack) 1192 layout.createText(stack, 'ne') 1193 1194 x, y = layout.XM, self.height-layout.YS 1195 s.talon = InitialDealTalonStack(x, y, self) 1196 1197 # define stack-groups 1198 layout.defaultStackGroups() 1199 1200 def startGame(self): 1201 self._startDealNumRows(7) 1202 self.s.talon.dealRow() 1203 self.s.talon.dealRowAvail() 1204 1205 shallHighlightMatch = Game._shallHighlightMatch_RKW 1206 1207 1208class Flake2Decks(Flake): 1209 def createGame(self): 1210 Flake.createGame(self, rows=8, playcards=22) 1211 1212 def startGame(self): 1213 self._startDealNumRowsAndDealSingleRow(12) 1214 1215 1216# ************************************************************************ 1217# * Beacon 1218# ************************************************************************ 1219 1220class Beacon(Game): 1221 1222 def createGame(self, rows=8): 1223 # create layout 1224 layout, s = Layout(self), self.s 1225 1226 # set window 1227 playcards = 12 1228 self.setSize( 1229 layout.XM+rows*layout.XS, 1230 layout.YM+3*layout.YS+playcards*layout.YOFFSET) 1231 1232 # create stacks 1233 x, y = layout.XM + (rows-1)*layout.XS//2, layout.YM 1234 stack = RK_FoundationStack(x, y, self, base_rank=ANY_RANK, 1235 max_cards=52, mod=13) 1236 s.foundations.append(stack) 1237 layout.createText(stack, 'ne') 1238 1239 x, y = layout.XM, layout.YM+layout.YS 1240 for i in range(rows): 1241 s.rows.append(RK_RowStack(x, y, self, base_rank=NO_RANK, mod=13)) 1242 x += layout.XS 1243 1244 x, y = layout.XM, self.height-layout.YS 1245 s.talon = TalonStack(x, y, self) 1246 layout.createText(s.talon, 'se') 1247 1248 # define stack-groups 1249 layout.defaultStackGroups() 1250 1251 def startGame(self): 1252 self._startDealNumRowsAndDealSingleRow(3) 1253 1254 def fillStack(self, stack): 1255 if stack in self.s.rows and not stack.cards: 1256 if self.s.talon.cards: 1257 old_state = self.enterState(self.S_FILL) 1258 self.s.talon.flipMove() 1259 self.s.talon.moveMove(1, stack) 1260 self.leaveState(old_state) 1261 1262 shallHighlightMatch = Game._shallHighlightMatch_RKW 1263 1264 1265# register the game 1266registerGame(GameInfo(36, Golf, "Golf", 1267 GI.GT_GOLF, 1, 0, GI.SL_BALANCED)) 1268registerGame(GameInfo(259, DeadKingGolf, "Dead King Golf", 1269 GI.GT_GOLF, 1, 0, GI.SL_BALANCED)) 1270registerGame(GameInfo(260, RelaxedGolf, "Relaxed Golf", 1271 GI.GT_GOLF | GI.GT_RELAXED, 1, 0, GI.SL_BALANCED, 1272 altnames=("Putt Putt",))) 1273registerGame(GameInfo(40, Elevator, "Elevator", 1274 GI.GT_GOLF, 1, 0, GI.SL_BALANCED, 1275 altnames=("Egyptian Solitaire", "Pyramid Golf"))) 1276registerGame(GameInfo(98, BlackHole, "Black Hole", 1277 GI.GT_GOLF | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL)) 1278registerGame(GameInfo(267, FourLeafClovers, "Four Leaf Clovers", 1279 GI.GT_GOLF | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL)) 1280registerGame(GameInfo(281, Escalator, "Escalator", 1281 GI.GT_GOLF, 1, 0, GI.SL_BALANCED)) 1282registerGame(GameInfo(405, AllInARow, "All in a Row", 1283 GI.GT_GOLF | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL)) 1284registerGame(GameInfo(432, Robert, "Robert", 1285 GI.GT_GOLF, 1, 2, GI.SL_LUCK)) 1286registerGame(GameInfo(551, DiamondMine, "Diamond Mine", 1287 GI.GT_1DECK_TYPE, 1, 0, GI.SL_BALANCED)) 1288registerGame(GameInfo(661, Dolphin, "Dolphin", 1289 GI.GT_GOLF | GI.GT_OPEN | GI.GT_ORIGINAL, 1, 0, 1290 GI.SL_MOSTLY_SKILL)) 1291registerGame(GameInfo(662, DoubleDolphin, "Double Dolphin", 1292 GI.GT_GOLF | GI.GT_OPEN | GI.GT_ORIGINAL, 2, 0, 1293 GI.SL_MOSTLY_SKILL)) 1294registerGame(GameInfo(709, Waterfall, "Waterfall", 1295 GI.GT_2DECK_TYPE | GI.GT_ORIGINAL, 2, 0, 1296 GI.SL_MOSTLY_SKILL)) 1297registerGame(GameInfo(720, Vague, "Vague", 1298 GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK)) 1299registerGame(GameInfo(723, DevilsSolitaire, "Devil's Solitaire", 1300 GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED, 1301 altnames=('Banner',))) 1302registerGame(GameInfo(728, ThirtyTwoCards, "Thirty Two Cards", 1303 GI.GT_2DECK_TYPE, 2, 0, GI.SL_LUCK)) 1304registerGame(GameInfo(731, ThreeFirTrees, "Three Fir-trees", 1305 GI.GT_GOLF, 2, 0, GI.SL_BALANCED)) 1306registerGame(GameInfo(733, NapoleonTakesMoscow, "Napoleon Takes Moscow", 1307 GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED)) 1308registerGame(GameInfo(734, NapoleonLeavesMoscow, "Napoleon Leaves Moscow", 1309 GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED, 1310 altnames=("Napoleon at Friedland",))) 1311registerGame(GameInfo(749, Flake, "Flake", 1312 GI.GT_GOLF | GI.GT_OPEN | GI.GT_ORIGINAL, 1313 1, 0, GI.SL_MOSTLY_SKILL)) 1314registerGame(GameInfo(750, Flake2Decks, "Flake (2 decks)", 1315 GI.GT_GOLF | GI.GT_OPEN | GI.GT_ORIGINAL, 1316 2, 0, GI.SL_MOSTLY_SKILL)) 1317registerGame(GameInfo(763, Wasatch, "Wasatch", 1318 GI.GT_GOLF, 1, UNLIMITED_REDEALS, 1319 GI.SL_MOSTLY_LUCK)) 1320registerGame(GameInfo(764, Beacon, "Beacon", 1321 GI.GT_1DECK_TYPE | GI.GT_ORIGINAL, 1, 0, 1322 GI.SL_MOSTLY_SKILL)) 1323registerGame(GameInfo(768, RelaxedThreeFirTrees, "Relaxed Three Fir-trees", 1324 GI.GT_GOLF | GI.GT_RELAXED, 2, 0, GI.SL_BALANCED)) 1325registerGame(GameInfo(777, DoubleGolf, "Double Golf", 1326 GI.GT_GOLF, 2, 0, GI.SL_BALANCED)) 1327registerGame(GameInfo(783, Uintah, "Uintah", 1328 GI.GT_GOLF, 1, UNLIMITED_REDEALS, 1329 GI.SL_MOSTLY_LUCK)) 1330registerGame(GameInfo(812, Sticko, "Sticko", 1331 GI.GT_1DECK_TYPE, 1, 0, GI.SL_BALANCED, 1332 ranks=(0, 6, 7, 8, 9, 10, 11, 12))) 1333