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
24from pysollib.game import Game
25from pysollib.gamedb import GI, GameInfo, registerGame
26from pysollib.games.bakersdozen import Cruel_Talon
27from pysollib.games.braid import Braid, Braid_ReserveStack, Braid_RowStack
28from pysollib.games.braid import Braid_BraidStack, Braid_Foundation
29from pysollib.hint import CautiousDefaultHint
30from pysollib.layout import Layout
31from pysollib.mfxutil import kwdefault
32from pysollib.mygettext import _
33from pysollib.pysoltk import MfxCanvasText
34from pysollib.stack import \
35        InitialDealTalonStack, \
36        OpenStack, \
37        RK_RowStack, \
38        ReserveStack, \
39        SS_FoundationStack, \
40        SS_RowStack, \
41        StackWrapper, \
42        WasteStack, \
43        WasteTalonStack
44from pysollib.util import ANY_RANK, NO_RANK, UNLIMITED_ACCEPTS, UNLIMITED_MOVES
45
46# ************************************************************************
47# * Tarock Talon Stacks
48# ************************************************************************
49
50
51class Wicked_Talon(Cruel_Talon):
52    pass
53
54
55# ************************************************************************
56# * Tarock Foundation Stacks
57# ************************************************************************
58
59class ImperialTrump_Foundation(SS_FoundationStack):
60    def acceptsCards(self, from_stack, cards):
61        if not SS_FoundationStack.acceptsCards(self, from_stack, cards):
62            return 0
63        return cards[-1].rank < len(self.game.s.foundations[4].cards)
64
65
66class Ponytail_Foundation(Braid_Foundation):
67    pass
68
69
70# ************************************************************************
71# * Tarock Row Stacks
72# ************************************************************************
73
74class Tarock_OpenStack(OpenStack):
75    def __init__(self, x, y, game, yoffset=-1, **cap):
76        kwdefault(cap, max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS)
77        OpenStack.__init__(self, x, y, game, **cap)
78        if yoffset < 0:
79            yoffset = game.app.images.CARD_YOFFSET
80        self.CARD_YOFFSET = yoffset
81
82
83class Tarock_AC_RowStack(Tarock_OpenStack):
84    def acceptsCards(self, from_stack, cards):
85        if not self.basicAcceptsCards(from_stack, cards):
86            return 0
87        if not self.cards:
88            return 1
89        if cards[0].rank != self.cards[-1].rank - 1:
90            return 0
91        elif cards[0].color == 2 or self.cards[-1].color == 2:
92            return 1
93        else:
94            return cards[0].color != self.cards[-1].color
95
96
97class Skiz_RowStack(RK_RowStack):
98    def acceptsCards(self, from_stack, cards):
99        if not self.basicAcceptsCards(from_stack, cards):
100            return 0
101        if not self.cards:
102            if cards[0].suit == len(self.game.gameinfo.suits):
103                return cards[0].rank == len(self.game.gameinfo.trumps) - 1
104            else:
105                return cards[0].rank == len(self.game.gameinfo.ranks) - 1
106        return self.cards[-1].suit == cards[0].suit and \
107            self.cards[-1].rank - 1 == cards[0].rank
108
109
110class Pagat_RowStack(RK_RowStack):
111    def acceptsCards(self, from_stack, cards):
112        if not self.basicAcceptsCards(from_stack, cards):
113            return 0
114        if not self.cards:
115            return 1
116        return self.cards[-1].suit == cards[0].suit and \
117            self.cards[-1].rank - 1 == cards[0].rank
118
119
120class TrumpWild_RowStack(Tarock_OpenStack):
121    def acceptsCards(self, from_stack, cards):
122        if not self.basicAcceptsCards(from_stack, cards):
123            return 0
124        if not self.cards:
125            if cards[0].suit == len(self.game.gameinfo.suits):
126                return cards[0].rank == len(self.game.gameinfo.trumps) - 1
127            else:
128                return cards[0].rank == len(self.game.gameinfo.ranks) - 1
129        if cards[0].rank != self.cards[-1].rank - 1:
130            return 0
131        elif cards[0].color == 2 or self.cards[-1].color == 2:
132            return 1
133        else:
134            return cards[0].color != self.cards[-1].color
135
136
137class TrumpOnly_RowStack(Tarock_OpenStack):
138    def acceptsCards(self, from_stack, cards):
139        if not self.basicAcceptsCards(from_stack, cards):
140            return 0
141        if not self.cards:
142            return cards[0].suit == len(self.game.gameinfo.suits)
143        return cards[0].color == 2 and cards[0].rank == self.cards[-1].rank - 1
144
145    def getBottomImage(self):
146        return self.game.app.images.getReserveBottom()
147
148
149class Excuse_RowStack(Tarock_OpenStack):
150    def acceptsCards(self, from_stack, cards):
151        if not self.basicAcceptsCards(from_stack, cards):
152            return 0
153        if not self.cards:
154            return 0
155        return cards[0].rank == self.cards[-1].rank - 1
156
157
158class WheelOfFortune_RowStack(Tarock_OpenStack):
159    def acceptsCards(self, from_stack, cards):
160        if not self.basicAcceptsCards(from_stack, cards):
161            return 0
162        if not self.cards:
163            return 1
164        return ((cards[0].suit == self.cards[-1].suit) and
165                (cards[0].rank == self.cards[-1].rank - 1))
166
167    def getBottomImage(self):
168        return self.game.app.images.getReserveBottom()
169
170
171class Ponytail_PonytailStack(Braid_BraidStack):
172    pass
173
174
175class Ponytail_RowStack(Braid_RowStack):
176    pass
177
178
179class Ponytail_ReserveStack(Braid_ReserveStack):
180    pass
181
182
183class Cavalier_RowStack(Tarock_AC_RowStack):
184    def acceptsCards(self, from_stack, cards):
185        if not Tarock_AC_RowStack.acceptsCards(self, from_stack, cards):
186            return 0
187        return self.cards or len(cards) == 1
188
189    def canMoveCards(self, cards):
190        for i in range(len(cards) - 1):
191            if not cards[i].suit == 4:
192                if cards[i].color == cards[i + 1].color:
193                    return 0
194            if cards[i].rank - 1 != cards[i + 1].rank:
195                return 0
196        return 1
197
198
199class Nasty_RowStack(SS_RowStack):
200    def acceptsCards(self, from_stack, cards):
201        if not self.basicAcceptsCards(from_stack, cards):
202            return 0
203        if self.cards:
204            return (cards[0].rank == self.cards[-1].rank - 1 and
205                    cards[0].suit == self.cards[-1].suit)
206        return cards[0].rank == 13 + 8 * (cards[0].suit == 4)
207
208
209# ************************************************************************
210# *
211# ************************************************************************
212
213class Tarock_GameMethods:
214    SUITS = (_("Wand"), _("Sword"), _("Cup"), _("Coin"), _("Trump"))
215    RANKS = (_("Ace"), "2", "3", "4", "5", "6", "7", "8", "9", "10",
216             _("Page"), _("Valet"), _("Queen"), _("King"))
217
218    def getCardFaceImage(self, deck, suit, rank):
219        return self.app.images.getFace(deck, suit, rank)
220
221
222class AbstractTarockGame(Tarock_GameMethods, Game):
223    pass
224
225
226# ************************************************************************
227# * Wheel of Fortune
228# ************************************************************************
229
230class WheelOfFortune(AbstractTarockGame):
231    Hint_Class = CautiousDefaultHint
232
233    #
234    # Game layout
235    #
236
237    def createGame(self):
238        l, s = Layout(self), self.s
239
240        # Set window size
241        self.setSize(l.XM + l.XS * 11.5, l.YM + l.YS * 5.5)
242
243        # Create wheel
244        xoffset = (1, 2, 3, 3.9, 3, 2, 1, 0, -1, -2, -3,
245                   -3.9, -3, -2, -1, 0, -2, -1, 0, 1, 2)
246        yoffset = (0.2, 0.5, 1.1, 2.2, 3.3, 3.9, 4.2, 4.4,
247                   4.2, 3.9, 3.3, 2.2, 1.1, 0.5, 0.2, 0,
248                   1.8, 2.1, 2.2, 2.4, 2.6)
249        x = l.XM + l.XS * 4
250        y = l.YM
251        for i in range(21):
252            x0 = x + xoffset[i] * l.XS
253            y0 = y + yoffset[i] * l.YS
254            s.rows.append(WheelOfFortune_RowStack(x0, y0, self,
255                          yoffset=l.CH//4,
256                          max_cards=2, max_move=1, max_accept=1))
257        self.setRegion(s.rows, (-999, -999, l.XS * 9, 999999))
258
259        # Create foundations
260        x = self.width - l.XS * 2
261        y = l.YM
262        s.foundations.append(SS_FoundationStack(x, y, self, 0, max_cards=14))
263        x = x + l.XS
264        s.foundations.append(SS_FoundationStack(x, y, self, 1, max_cards=14))
265        y = y + l.YS
266        s.foundations.append(SS_FoundationStack(x, y, self, 3, max_cards=14))
267        x = x - l.XS
268        s.foundations.append(SS_FoundationStack(x, y, self, 2, max_cards=14))
269        x = x + l.XS * 0.5
270        y = y + l.YS
271        s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22))
272
273        # Create talon
274        x = self.width - l.XS
275        y = self.height - l.YS * 1.5
276        s.talon = WasteTalonStack(x, y, self, num_deal=2, max_rounds=1)
277        l.createText(s.talon, "n")
278        x = x - l.XS
279        s.waste = WasteStack(x, y, self)
280        l.createText(s.waste, "n")
281
282        # Define stack groups
283        l.defaultStackGroups()
284
285    #
286    # Game over rides
287    #
288
289    def startGame(self):
290        assert len(self.s.talon.cards) == 78
291        self.startDealSample()
292        self.s.talon.dealRow(rows=self.s.rows[-5:])
293        self.s.talon.dealRow(rows=self.s.rows[4:-5])
294        self.s.talon.dealRow(rows=self.s.rows[:4])
295        self.s.talon.dealCards()
296
297    def shallHighlightMatch(self, stack1, card1, stack2, card2):
298        return 0
299
300
301# ************************************************************************
302# * Imperial Trumps
303# ************************************************************************
304
305class ImperialTrumps(AbstractTarockGame):
306
307    #
308    # Game layout
309    #
310
311    def createGame(self):
312        l, s = Layout(self), self.s
313
314        # Set window size
315        self.setSize(l.XM + l.XS * 8, l.YM + l.YS * 5)
316
317        # Create foundations
318        x = l.XM + l.XS * 3
319        y = l.YM
320        for i in range(4):
321            s.foundations.append(
322                ImperialTrump_Foundation(x, y, self, i, max_cards=14))
323            x = x + l.XS
324        s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22))
325
326        # Create talon
327        x = l.XM
328        s.talon = WasteTalonStack(x, y, self, num_deal=1, max_rounds=-1)
329        l.createText(s.talon, "s")
330        x = x + l.XS
331        s.waste = WasteStack(x, y, self)
332        l.createText(s.waste, "s")
333
334        # Create rows
335        x = l.XM
336        y = l.YM + l.YS + l.TEXT_HEIGHT
337        for i in range(8):
338            s.rows.append(TrumpWild_RowStack(x, y, self))
339            x = x + l.XS
340        self.setRegion(s.rows, (-999, y, 999999, 999999))
341
342        # Define stack groups
343        l.defaultStackGroups()
344
345    #
346    # Game over rides
347    #
348
349    def startGame(self, reverse=1):
350        assert len(self.s.talon.cards) == 78
351        for i in range(1, len(self.s.rows)):
352            self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0)
353        self.startDealSample()
354        self.s.talon.dealRow(reverse=reverse)
355        self.s.talon.dealCards()
356
357    def shallHighlightMatch(self, stack1, card1, stack2, card2):
358        return 0
359
360
361# ************************************************************************
362# * Pagat
363# ************************************************************************
364
365class Pagat(AbstractTarockGame):
366
367    #
368    # Game layout
369    #
370
371    def createGame(self):
372        l, s = Layout(self), self.s
373
374        # Set window size
375        h = max(3 * l.YS, 20 * l.YOFFSET)
376        self.setSize(l.XM + 12 * l.XS, l.YM + l.YS + h)
377
378        # Create foundations
379        x = l.XM + l.XS * 3.5
380        y = l.YM
381        s.foundations.append(SS_FoundationStack(x, y, self, 0, max_cards=14))
382        x = x + l.XS
383        s.foundations.append(SS_FoundationStack(x, y, self, 1, max_cards=14))
384        x = x + l.XS
385        s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22))
386        x = x + l.XS
387        s.foundations.append(SS_FoundationStack(x, y, self, 2, max_cards=14))
388        x = x + l.XS
389        s.foundations.append(SS_FoundationStack(x, y, self, 3, max_cards=14))
390
391        # Create reserves
392        x = l.XM
393        for i in range(3):
394            s.reserves.append(ReserveStack(x, y, self))
395            x = x + l.XS
396        x = x + l.XS * 6
397        for i in range(3):
398            s.reserves.append(ReserveStack(x, y, self))
399            x = x + l.XS
400
401        # Create rows
402        x = l.XM
403        y = l.YM + l.YS * 1.1
404        for i in range(12):
405            s.rows.append(Pagat_RowStack(x, y, self))
406            x = x + l.XS
407        self.setRegion(s.rows, (-999, int(y), 999999, 999999))
408
409        # Create talon
410        s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self)
411
412        # Define stack groups
413        l.defaultStackGroups()
414
415    #
416    # Game over rides
417    #
418
419    def startGame(self):
420        assert len(self.s.talon.cards) == 78
421        self._startDealNumRows(6)
422        self.s.talon.dealRow(rows=self.s.rows[3:9])
423
424    def shallHighlightMatch(self, stack1, card1, stack2, card2):
425        return (card1.suit == card2.suit and
426                (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank))
427
428
429# ************************************************************************
430# * Skiz
431# ************************************************************************
432
433class Skiz(AbstractTarockGame):
434
435    #
436    # Game layout
437    #
438
439    def createGame(self):
440        l, s = Layout(self), self.s
441
442        # Set window size
443        h = max(3 * l.YS, 20 * l.YOFFSET)
444        self.setSize(l.XM + 12 * l.XS, l.YM + l.YS + h)
445
446        # Create foundations
447        x = l.XM + l.XS * 3.5
448        y = l.YM
449        s.foundations.append(SS_FoundationStack(x, y, self, 0, max_cards=14))
450        x = x + l.XS
451        s.foundations.append(SS_FoundationStack(x, y, self, 1, max_cards=14))
452        x = x + l.XS
453        s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22))
454        x = x + l.XS
455        s.foundations.append(SS_FoundationStack(x, y, self, 2, max_cards=14))
456        x = x + l.XS
457        s.foundations.append(SS_FoundationStack(x, y, self, 3, max_cards=14))
458
459        # Create reserves
460        x = l.XM
461        for i in range(3):
462            s.reserves.append(ReserveStack(x, y, self))
463            x = x + l.XS
464        x = x + l.XS * 6
465        for i in range(3):
466            s.reserves.append(ReserveStack(x, y, self))
467            x = x + l.XS
468
469        # Create rows
470        x = l.XM
471        y = l.YM + l.YS * 1.1
472        for i in range(12):
473            s.rows.append(Skiz_RowStack(x, y, self))
474            x = x + l.XS
475        self.setRegion(s.rows, (-999, int(y), 999999, 999999))
476
477        # Create talon
478        s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self)
479
480        # Define stack groups
481        l.defaultStackGroups()
482
483    #
484    # Game over rides
485    #
486
487    def startGame(self):
488        assert len(self.s.talon.cards) == 78
489        self._startDealNumRows(6)
490        self.s.talon.dealRow(rows=self.s.rows[3:9])
491
492    def shallHighlightMatch(self, stack1, card1, stack2, card2):
493        return (card1.suit == card2.suit and
494                (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank))
495
496
497# ************************************************************************
498# * Fifteen Plus
499# ************************************************************************
500
501class FifteenPlus(AbstractTarockGame):
502    Hint_Class = CautiousDefaultHint
503
504    #
505    # Game layout
506    #
507
508    def createGame(self):
509        l, s = Layout(self), self.s
510
511        # Set window size
512        h = max(5 * l.YS, 20 * l.YOFFSET)
513        self.setSize(l.XM + 9 * l.XS, l.YM + l.YS + h)
514
515        # Create foundations
516        x = self.width - l.XS
517        y = l.YM
518        s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22))
519        y = y + l.YS
520        for i in range(4):
521            s.foundations.append(
522                SS_FoundationStack(x, y, self, i, max_cards=14))
523            y = y + l.YS
524
525        # Create rows
526        x = l.XM
527        y = l.YM
528        for j in range(2):
529            for i in range(8):
530                s.rows.append(
531                    Tarock_AC_RowStack(x, y, self, max_move=1, max_accept=1))
532                x = x + l.XS
533            x = l.XM
534            y = y + l.YS * 3
535        self.setRegion(s.rows, (-999, -999, l.XM + l.XS * 8, 999999))
536
537        # Create talon
538        s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self)
539
540        # Define stack groups
541        l.defaultStackGroups()
542
543    #
544    # Game over rides
545    #
546
547    def startGame(self):
548        assert len(self.s.talon.cards) == 78
549        for i in range(2):
550            self.s.talon.dealRow(flip=0, frames=0)
551        for i in range(2):
552            self.s.talon.dealRow(rows=self.s.rows[:15], flip=0, frames=0)
553        self._startAndDealRow()
554
555    def shallHighlightMatch(self, stack1, card1, stack2, card2):
556        return (card1.suit == card2.suit and
557                (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank))
558
559
560# ************************************************************************
561# * Excuse
562# ************************************************************************
563
564class Excuse(AbstractTarockGame):
565    Hint_Class = CautiousDefaultHint
566    GAME_VERSION = 2
567
568    #
569    # Game layout
570    #
571
572    def createGame(self):
573        l, s = Layout(self), self.s
574
575        # Set window size
576        h = max(5 * l.YS, 20 * l.YOFFSET)
577        self.setSize(l.XM + 9 * l.XS, l.YM + l.YS + h)
578
579        # Create foundations
580        x = self.width - l.XS
581        y = l.YM
582        s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22))
583        y = y + l.YS
584        for i in range(4):
585            s.foundations.append(
586                SS_FoundationStack(x, y, self, i, max_cards=14))
587            y = y + l.YS
588
589        # Create rows
590        x = l.XM
591        y = l.YM
592        for j in range(2):
593            for i in range(8):
594                s.rows.append(Excuse_RowStack(x, y, self,
595                              max_move=1, max_accept=1, base_rank=NO_RANK))
596                x = x + l.XS
597            x = l.XM
598            y = y + l.YS * 3
599        self.setRegion(s.rows, (-999, -999, l.XM + l.XS * 8, 999999))
600
601        # Create talon
602        s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self)
603
604        # Define stack groups
605        l.defaultStackGroups()
606
607    #
608    # Game over rides
609    #
610
611    def _shuffleHook(self, cards):
612        # move Kings to bottom of each stack (see Baker's Dozen)
613        def isKing(c):
614            return ((c.suit < 4 and c.rank == 13) or
615                    (c.suit == 4 and c.rank == 21))
616        i, n = 0, len(self.s.rows)
617        kings = []
618        for c in cards:
619            if isKing(c):
620                kings.append(i)
621            i = i + 1
622        for i in kings:
623            j = i % n
624            while j < i:
625                if not isKing(cards[j]):
626                    cards[i], cards[j] = cards[j], cards[i]
627                    break
628                j = j + n
629        cards.reverse()
630        return cards
631
632    def startGame(self):
633        assert len(self.s.talon.cards) == 78
634        self._dealNumRows(3)
635        self.s.talon.dealRow(rows=self.s.rows[:15], frames=0)
636        self.startDealSample()
637        self.s.talon.dealRow(rows=self.s.rows[:15])
638
639    def shallHighlightMatch(self, stack1, card1, stack2, card2):
640        return (card1.rank + 1 == card2.rank or card1.rank - 1 == card2.rank)
641
642
643# ************************************************************************
644# * Grasshopper
645# * Double Grasshopper
646# ************************************************************************
647
648class Grasshopper(AbstractTarockGame):
649    GAME_VERSION = 2
650    MAX_ROUNDS = 2
651
652    #
653    # Game layout
654    #
655
656    def createGame(self):
657        l, s = Layout(self), self.s
658
659        # Set window size
660        decks = self.gameinfo.decks
661        self.setSize(2*l.XM + (2 + 5*decks)*l.XS, 3*l.YM + 5*l.YS)
662        yoffset = min(l.YOFFSET, max(10, l.YOFFSET // 2))
663
664        # Create talon
665        x = l.XM
666        y = l.YM
667        s.talon = WasteTalonStack(
668            x, y, self, num_deal=1, max_rounds=self.MAX_ROUNDS)
669        l.createText(s.talon, "s")
670        x = x + l.XS
671        s.waste = WasteStack(x, y, self)
672        l.createText(s.waste, "s")
673
674        # Create foundations
675        x = x + l.XM + l.XS
676        for j in range(4):
677            for i in range(decks):
678                s.foundations.append(
679                    SS_FoundationStack(x, y, self, j, max_cards=14))
680                x = x + l.XS
681        for i in range(decks):
682            s.foundations.append(
683                SS_FoundationStack(x, y, self, 4, max_cards=22))
684            x = x + l.XS
685
686        # Create reserve
687        x = l.XM
688        y = l.YM + l.YS + l.TEXT_HEIGHT
689        s.reserves.append(OpenStack(x, y, self))
690        s.reserves[0].CARD_YOFFSET = (l.YOFFSET, yoffset)[decks == 2]
691
692        # Create rows
693        x = x + l.XM + l.XS
694        for i in range(decks):
695            s.rows.append(TrumpOnly_RowStack(x, y, self, yoffset=yoffset))
696            x = x + l.XS
697        for i in range(4*decks+1):
698            s.rows.append(Tarock_AC_RowStack(x, y, self))
699            x = x + l.XS
700        self.setRegion(s.rows, (-999, y - l.YS, 999999, 999999))
701
702        # Define stack groups
703        l.defaultStackGroups()
704
705    #
706    # Game over rides
707    #
708
709    def startGame(self):
710        decks = self.gameinfo.decks
711        assert len(self.s.talon.cards) == 78 * decks
712        self.startDealSample()
713        for i in range(14 * decks):
714            self.s.talon.dealRow(rows=self.s.reserves, flip=0, frames=4)
715        self.s.reserves[0].flipMove()
716        self.s.talon.dealRow(rows=self.s.rows[decks:])
717        self.s.talon.dealCards()          # deal first card to WasteStack
718
719    def fillStack(self, stack):
720        r = self.s.reserves[0]
721        if not stack.cards and stack in self.s.rows:
722            if r.cards and stack.acceptsCards(r, r.cards[-1:]):
723                r.moveMove(1, stack)
724        if r.canFlipCard():
725            r.flipMove()
726
727    def shallHighlightMatch(self, stack1, card1, stack2, card2):
728        return ((card1.rank + 1 == card2.rank or
729                card1.rank - 1 == card2.rank) and
730                card1.color != card2.color)
731
732
733class DoubleGrasshopper(Grasshopper):
734    pass
735
736
737# ************************************************************************
738# * Ponytail
739# ************************************************************************
740
741class Ponytail(Tarock_GameMethods, Braid):
742
743    #
744    # game layout
745    #
746
747    def createGame(self):
748        # create layout
749        l, s = Layout(self), self.s
750
751        # set window
752        # (piles up to 20 cards are playable -
753        #       needed for Ponytail_PonytailStack)
754        h = max(5*l.YS + l.TEXT_HEIGHT, l.YS+(self.BRAID_CARDS-1)*l.YOFFSET)
755        self.setSize(10*l.XS+l.XM, l.YM + h)
756
757        # extra settings
758        self.base_card = None
759
760        # create stacks
761        s.addattr(braid=None)      # register extra stack variable
762        x, y = l.XM, l.YM
763        for i in range(2):
764            s.rows.append(Ponytail_RowStack(x + 0.5 * l.XS, y, self))
765            s.rows.append(Ponytail_RowStack(x + 4.5 * l.XS, y, self))
766            s.rows.append(Ponytail_RowStack(x + 5.5 * l.XS, y, self))
767            s.rows.append(Ponytail_RowStack(x + 6.5 * l.XS, y, self))
768            y = y + 4 * l.YS
769        y = l.YM + l.YS
770        for i in range(2):
771            s.rows.append(Ponytail_ReserveStack(x, y, self))
772            s.rows.append(Ponytail_ReserveStack(x + l.XS, y, self))
773            s.rows.append(Ponytail_ReserveStack(x, y + l.YS, self))
774            s.rows.append(Ponytail_ReserveStack(x + l.XS, y + l.YS, self))
775            s.rows.append(Ponytail_ReserveStack(x, y + 2 * l.YS, self))
776            s.rows.append(Ponytail_ReserveStack(x + l.XS, y + 2 * l.YS, self))
777            x = x + 4 * l.XS
778        x = l.XM + 5*l.XS//2
779        y = l.YM
780        s.braid = Ponytail_PonytailStack(x, y, self, sine=1)
781        x = l.XM + 7 * l.XS
782        y = l.YM + 2*l.YS
783        s.talon = WasteTalonStack(x, y, self, max_rounds=3)
784        l.createText(s.talon, "s")
785        s.talon.texts.rounds = MfxCanvasText(
786            self.canvas,
787            x + l.CW // 2, y - l.YM,
788            anchor="s",
789            font=self.app.getFont("canvas_default"))
790        x = x - l.XS
791        s.waste = WasteStack(x, y, self)
792        l.createText(s.waste, "s")
793        x = l.XM + 8 * l.XS
794        y = l.YM
795        for i in range(4):
796            s.foundations.append(
797                Ponytail_Foundation(x, y, self, i, mod=14, max_cards=14))
798            s.foundations.append(
799                Ponytail_Foundation(
800                    x + l.XS, y, self, i, mod=14, max_cards=14))
801            y = y + l.YS
802        s.foundations.append(
803            Ponytail_Foundation(x, y, self, 4, mod=22, max_cards=22))
804        s.foundations.append(
805            Ponytail_Foundation(x + l.XS, y, self, 4, mod=22, max_cards=22))
806        # ???
807        self.texts.info = MfxCanvasText(
808            self.canvas,
809            x + l.CW + l.XM // 2, y + l.YS,
810            anchor="n",
811            font=self.app.getFont("canvas_default"))
812
813        # define stack-groups
814        self.sg.openstacks = s.foundations + s.rows
815        self.sg.talonstacks = [s.talon] + [s.waste]
816        self.sg.dropstacks = [s.braid] + s.rows + [s.waste]
817
818
819# ************************************************************************
820# * Cavalier
821# * Five Aces
822# * Wicked
823# * Nasty
824# ************************************************************************
825
826class Cavalier(AbstractTarockGame):
827    Layout_Method = staticmethod(Layout.bakersDozenLayout)
828    Talon_Class = InitialDealTalonStack
829    Foundation_Class = SS_FoundationStack
830    RowStack_Class = Cavalier_RowStack
831
832    #
833    # Game layout
834    #
835
836    def createGame(self, **layout):
837        # Create layout
838        l, s = Layout(self), self.s
839        kwdefault(layout, rows=18, playcards=19)
840        self.Layout_Method(l, **layout)
841        self.setSize(l.size[0], l.size[1])
842
843        # Create foundations
844        for r in l.s.foundations:
845            n = 14 + 8 * (r.suit == 4)
846            s.foundations.append(self.Foundation_Class(r.x, r.y, self, r.suit,
847                                 mod=n, max_cards=n))
848
849        # Create rows
850        for r in l.s.rows:
851            s.rows.append(self.RowStack_Class(r.x, r.y, self))
852
853        # Create talon
854        s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self)
855
856        # Define stack groups
857        l.defaultAll()
858
859    #
860    # Game over rides
861    #
862
863    def startGame(self, flip=(0, 1, 0), foundations=0):
864        assert len(self.s.talon.cards) == 78
865        for f in flip:
866            self.s.talon.dealRow(flip=f, frames=0)
867        self.startDealSample()
868        self.s.talon.dealRow()
869        if foundations:
870            self.s.talon.dealRow(rows=self.s.rows[0:1])
871            self.s.talon.dealRow(rows=self.s.foundations)
872        else:
873            self.s.talon.dealRow(rows=self.s.rows[:6])
874
875    def shallHighlightMatch(self, stack1, card1, stack2, card2):
876        return ((card1.rank + 1 == card2.rank or
877                 card1.rank - 1 == card2.rank) and
878                ((card1.suit == 4 or card2.suit == 4) or
879                 card1.color != card2.color))
880
881
882class FiveAces(Cavalier):
883    def _shuffleHook(self, cards):
884        return self._shuffleHookMoveToBottom(
885            cards, lambda c: (c.rank == 0, c.suit))
886
887    def startGame(self):
888        Cavalier.startGame(self, foundations=1)
889
890
891class Wicked(FiveAces):
892    Talon_Class = StackWrapper(Wicked_Talon, max_rounds=-1)
893    RowStack_Class = StackWrapper(
894        SS_RowStack, max_move=1, max_accept=1, base_rank=NO_RANK)
895    Hint_Class = CautiousDefaultHint
896
897    def startGame(self):
898        Cavalier.startGame(self, flip=(1, 1, 1), foundations=1)
899
900    def shallHighlightMatch(self, stack1, card1, stack2, card2):
901        return ((card1.rank + 1 == card2.rank or
902                 card1.rank - 1 == card2.rank) and
903                card1.suit == card2.suit)
904
905
906class Nasty(Wicked):
907    RowStack_Class = StackWrapper(
908        Nasty_RowStack, max_move=1, max_accept=1, base_rank=ANY_RANK)
909
910
911# ************************************************************************
912# * register the games
913# ************************************************************************
914
915def r(id, gameclass, name, game_type, decks, redeals, skill_level):
916    game_type = game_type | GI.GT_TAROCK | GI.GT_CONTRIB | GI.GT_ORIGINAL
917    gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level,
918                  ranks=list(range(14)), trumps=list(range(22)))
919    registerGame(gi)
920    return gi
921
922
923r(157, WheelOfFortune, "Wheel of Fortune", GI.GT_TAROCK, 1, 0, GI.SL_BALANCED)
924r(158, ImperialTrumps, "Imperial Trumps", GI.GT_TAROCK, 1, -1, GI.SL_BALANCED)
925r(159, Pagat, "Pagat", GI.GT_TAROCK | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL)
926r(160, Skiz, "Skiz", GI.GT_TAROCK | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL)
927r(161, FifteenPlus, "Fifteen plus", GI.GT_TAROCK, 1, 0, GI.SL_BALANCED)
928r(162, Excuse, "Excuse", GI.GT_TAROCK | GI.GT_OPEN, 1, 0, GI.SL_BALANCED)
929r(163, Grasshopper, "Grasshopper", GI.GT_TAROCK, 1, 1, GI.SL_MOSTLY_SKILL)
930r(164, DoubleGrasshopper, "Double Grasshopper", GI.GT_TAROCK, 2, 1,
931  GI.SL_MOSTLY_SKILL)
932r(179, Ponytail, "Ponytail", GI.GT_TAROCK, 2, 2, GI.SL_MOSTLY_SKILL)
933r(202, Cavalier, "Cavalier", GI.GT_TAROCK, 1, 0, GI.SL_MOSTLY_SKILL)
934r(203, FiveAces, "Five Aces", GI.GT_TAROCK, 1, 0, GI.SL_MOSTLY_SKILL)
935r(204, Wicked, "Wicked", GI.GT_TAROCK | GI.GT_OPEN, 1, -1, GI.SL_BALANCED)
936r(205, Nasty, "Nasty", GI.GT_TAROCK | GI.GT_OPEN, 1, -1, GI.SL_BALANCED)
937
938del r
939