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.spider import Spider_Hint, Spider_RowStack, \
27        Spider_SS_Foundation
28from pysollib.hint import KlondikeType_Hint, YukonType_Hint
29from pysollib.layout import Layout
30from pysollib.mfxutil import kwdefault
31from pysollib.stack import \
32        AC_RowStack, \
33        AbstractFoundationStack, \
34        BasicRowStack, \
35        DealRowTalonStack, \
36        InitialDealTalonStack, \
37        KingAC_RowStack, \
38        OpenStack, \
39        ReserveStack, \
40        SS_FoundationStack, \
41        SS_RowStack, \
42        Spider_SS_RowStack, \
43        Stack, \
44        StackWrapper, \
45        TalonStack, \
46        WasteStack, \
47        WasteTalonStack, \
48        Yukon_AC_RowStack
49from pysollib.util import ACE, ANY_SUIT, KING, UNLIMITED_ACCEPTS, \
50        UNLIMITED_MOVES
51
52# ************************************************************************
53# * Gypsy
54# ************************************************************************
55
56
57class Gypsy(Game):
58    Layout_Method = staticmethod(Layout.gypsyLayout)
59    Talon_Class = DealRowTalonStack
60    Foundation_Class = SS_FoundationStack
61    RowStack_Class = AC_RowStack
62    Hint_Class = KlondikeType_Hint
63
64    def createGame(self, **layout):
65        # create layout
66        l, s = Layout(self), self.s
67        kwdefault(layout, rows=8, waste=0, texts=1)
68        self.Layout_Method(l, **layout)
69        self.setSize(l.size[0], l.size[1])
70        # create stacks
71        s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self)
72        if l.s.waste:
73            s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self)
74        for r in l.s.foundations:
75            s.foundations.append(
76                self.Foundation_Class(r.x, r.y, self, suit=r.suit))
77        for r in l.s.rows:
78            s.rows.append(self.RowStack_Class(r.x, r.y, self))
79        # default
80        l.defaultAll()
81
82    def startGame(self):
83        for i in range(2):
84            self.s.talon.dealRow(flip=0, frames=0)
85        self._startAndDealRow()
86
87    shallHighlightMatch = Game._shallHighlightMatch_AC
88
89
90# ************************************************************************
91# * Giant
92# ************************************************************************
93
94class Giant_Foundation(SS_FoundationStack):
95    def canMoveCards(self, cards):
96        if not SS_FoundationStack.canMoveCards(self, cards):
97            return False
98        # can only move cards if the Talon is empty
99        return len(self.game.s.talon.cards) == 0
100
101
102class Giant(Gypsy):
103    Foundation_Class = Giant_Foundation
104
105    def startGame(self):
106        self._startAndDealRow()
107
108
109# ************************************************************************
110# * Irmgard
111# ************************************************************************
112
113class Irmgard_Talon(TalonStack):
114    # A single click deals 9 (or 7) new cards to the RowStacks.
115    def dealCards(self, sound=False):
116        if self.cards:
117            if len(self.cards) > 7:
118                c = self.dealRow(sound=sound)
119            else:
120                c = self.dealRow(self.game.s.rows[1:8], sound=sound)
121            return c
122        return 0
123
124
125class Irmgard(Gypsy):
126    GAME_VERSION = 2
127
128    Layout_Method = staticmethod(Layout.harpLayout)
129    Talon_Class = Irmgard_Talon
130    RowStack_Class = KingAC_RowStack
131
132    def createGame(self):
133        Gypsy.createGame(self, rows=9, playcards=19)
134
135    def startGame(self):
136        r = self.s.rows
137        for i in range(1, 5):
138            self.s.talon.dealRow(rows=r[i:len(r)-i], flip=0, frames=0)
139        self._startAndDealRow()
140
141
142# ************************************************************************
143# * Die Königsbergerin
144# ************************************************************************
145
146class DieKoenigsbergerin_Talon(DealRowTalonStack):
147    # all Aces go to Foundations
148    dealToStacks = DealRowTalonStack.dealToStacksOrFoundations
149
150
151class DieKoenigsbergerin(Gypsy):
152    Talon_Class = DieKoenigsbergerin_Talon
153    Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0)
154
155    def startGame(self):
156        self.startDealSample()
157        for i in range(3):
158            self.s.talon.dealRow()
159
160
161# ************************************************************************
162# * Die Russische
163# ************************************************************************
164
165class DieRussische_Foundation(AbstractFoundationStack):
166    def acceptsCards(self, from_stack, cards):
167        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
168            return False
169        if self.cards:
170            # check the rank - an ACE equals a Six
171            rank = self.cards[-1].rank
172            if rank == ACE:
173                rank = 5
174            if (rank + self.cap.dir) % self.cap.mod != cards[0].rank:
175                return False
176        return True
177
178
179class DieRussische_RowStack(AC_RowStack):
180    def acceptsCards(self, from_stack, cards):
181        if not AC_RowStack.acceptsCards(self, from_stack, cards):
182            return False
183        # when empty, only accept a single card
184        return self.cards or len(cards) == 1
185
186
187class DieRussische(Gypsy):
188    Talon_Class = InitialDealTalonStack
189    Foundation_Class = StackWrapper(
190        DieRussische_Foundation, min_cards=1, max_cards=8)
191    RowStack_Class = DieRussische_RowStack
192
193    def createGame(self):
194        Gypsy.createGame(self, rows=7, texts=0)
195
196    def _shuffleHook(self, cards):
197        # move one Ace to bottom of the Talon (i.e. last card to be dealt)
198        return self._shuffleHookMoveToBottom(
199            cards, lambda c: (c.rank == 0, c.suit), 1)
200
201    def startGame(self):
202        self._startDealNumRows(6)
203        for i in range(3):
204            self.s.talon.dealRow()
205        c = self.s.talon.cards[-1]
206        self.s.talon.dealRow(rows=(self.s.foundations[c.suit*2],))
207
208
209# ************************************************************************
210# * Miss Milligan
211# * Imperial Guards
212# ************************************************************************
213
214class MissMilligan_ReserveStack(AC_RowStack):
215    def acceptsCards(self, from_stack, cards):
216        if not AC_RowStack.acceptsCards(self, from_stack, cards):
217            return False
218        # Note that this reserve stack accepts sequences if both
219        # the reserve stack and the Talon are empty.
220        return len(self.cards) == 0 and len(self.game.s.talon.cards) == 0
221
222    getBottomImage = Stack._getReserveBottomImage
223
224
225class MissMilligan(Gypsy):
226    Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0)
227    RowStack_Class = KingAC_RowStack
228    ReserveStack_Class = MissMilligan_ReserveStack
229
230    def createGame(self, rows=8, reserves=1):
231        # create layout
232        l, s = Layout(self), self.s
233
234        # set window
235        self.setSize(
236            l.XM + (1+max(8, rows))*l.XS,
237            l.YM + (1+max(4, reserves))*l.YS+l.TEXT_HEIGHT)
238
239        # create stacks
240        x, y = l.XM, l.YM
241        s.talon = self.Talon_Class(x, y, self)
242        for i in range(8):
243            x = x + l.XS
244            s.foundations.append(self.Foundation_Class(x, y, self, suit=i//2))
245        x, y = l.XM, y + l.YS
246        rx, ry = x + l.XS - l.CW//2, y - l.CH//2
247        for i in range(reserves):
248            s.reserves.append(
249                self.ReserveStack_Class(x, y+l.TEXT_HEIGHT, self))
250            y = y + l.YS
251        l.createText(s.talon, "s")
252        if s.reserves:
253            self.setRegion(s.reserves, (-999, ry+l.TEXT_HEIGHT, rx-1, 999999))
254        else:
255            rx = -999
256        x, y = l.XM + (8-rows)*l.XS//2, l.YM + l.YS
257        for i in range(rows):
258            x = x + l.XS
259            s.rows.append(self.RowStack_Class(x, y, self))
260        self.setRegion(s.rows, (rx, ry, 999999, 999999))
261
262        # define stack-groups
263        l.defaultStackGroups()
264
265    def startGame(self):
266        self._startAndDealRow()
267
268
269class ImperialGuards(MissMilligan):
270    RowStack_Class = AC_RowStack
271
272
273# ************************************************************************
274# * Nomad
275# ************************************************************************
276
277class Nomad(MissMilligan):
278    Foundation_Class = SS_FoundationStack
279    RowStack_Class = AC_RowStack
280    ReserveStack_Class = ReserveStack
281
282    def startGame(self):
283        self._startDealNumRowsAndDealSingleRow(3)
284
285
286# ************************************************************************
287# * Milligan Cell
288# ************************************************************************
289
290class MilliganCell(MissMilligan):
291    ReserveStack_Class = ReserveStack
292
293    def createGame(self):
294        MissMilligan.createGame(self, reserves=4)
295
296    def startGame(self):
297        self._startAndDealRow()
298
299
300# ************************************************************************
301# * Milligan Harp
302# * Carlton
303# * Steve
304# ************************************************************************
305
306class MilliganHarp(Gypsy):
307    Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0)
308
309    def startGame(self, flip=0):
310        for i in range(len(self.s.rows)):
311            self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=flip, frames=0)
312        self._startAndDealRow()
313
314
315class Carlton(MilliganHarp):
316    def startGame(self):
317        MilliganHarp.startGame(self, flip=1)
318
319
320class Steve(Carlton):
321    Hint_Class = Spider_Hint
322    RowStack_Class = Spider_SS_RowStack
323
324    shallHighlightMatch = Game._shallHighlightMatch_RK
325    getQuickPlayScore = Game._getSpiderQuickPlayScore
326
327
328# ************************************************************************
329# * Lexington Harp
330# * Brunswick
331# * Mississippi
332# * Griffon
333# ************************************************************************
334
335class LexingtonHarp(MilliganHarp):
336    GAME_VERSION = 2
337    RowStack_Class = Yukon_AC_RowStack
338    Hint_Class = YukonType_Hint
339
340    def getHighlightPilesStacks(self):
341        return ()
342
343
344class Brunswick(LexingtonHarp):
345    def startGame(self):
346        LexingtonHarp.startGame(self, flip=1)
347
348
349class Mississippi(LexingtonHarp):
350    def createGame(self):
351        LexingtonHarp.createGame(self, rows=7)
352
353
354class Griffon(Mississippi):
355    def startGame(self):
356        Mississippi.startGame(self, flip=1)
357
358
359# ************************************************************************
360# * Blockade
361# * Phantom Blockade
362# ************************************************************************
363
364class Blockade(Gypsy):
365    Layout_Method = staticmethod(Layout.klondikeLayout)
366    RowStack_Class = SS_RowStack
367
368    def createGame(self):
369        Gypsy.createGame(self, rows=12)
370
371    def startGame(self):
372        self._startAndDealRow()
373
374    def fillStack(self, stack):
375        if stack in self.s.rows and not stack.cards and self.s.talon.cards:
376            old_state = self.enterState(self.S_FILL)
377            self.s.talon.flipMove()
378            self.s.talon.moveMove(1, stack)
379            self.leaveState(old_state)
380
381    shallHighlightMatch = Game._shallHighlightMatch_SS
382
383
384class PhantomBlockade(Gypsy):
385    Layout_Method = staticmethod(Layout.klondikeLayout)
386    RowStack_Class = KingAC_RowStack
387
388    def createGame(self):
389        Gypsy.createGame(self, rows=13, playcards=24)
390
391    def startGame(self):
392        self._startDealNumRowsAndDealSingleRow(2)
393
394
395# ************************************************************************
396# * Cone
397# ************************************************************************
398
399class Cone_Talon(DealRowTalonStack):
400    def canDealCards(self):
401        if not DealRowTalonStack.canDealCards(self):
402            return False
403        if len(self.cards) == 4:
404            return True
405        for r in self.game.s.rows:
406            if not r.cards:
407                return False
408        return True
409
410    def dealCards(self, sound=False):
411        rows = self.game.s.rows
412        if len(self.cards) == 4:
413            rows = self.game.s.reserves
414        return self.dealRowAvail(rows=rows, sound=sound)
415
416
417class Cone(Gypsy):
418
419    def createGame(self):
420        # create layout
421        l, s = Layout(self), self.s
422
423        # set window
424        self.setSize(l.XM+9*l.XS, 3*l.YM+5*l.YS)
425
426        # create stacks
427        x, y = l.XM, l.YM
428        s.talon = Cone_Talon(x, y, self)
429        l.createText(s.talon, 's')
430        y += l.YS+2*l.YM
431        for i in range(4):
432            s.reserves.append(OpenStack(x, y, self, max_accept=0))
433            y += l.YS
434        x, y = l.XM+l.XS, l.YM
435        for i in range(7):
436            s.rows.append(AC_RowStack(x, y, self, mod=13))
437            x += l.XS
438        for i in range(4):
439            s.foundations.append(SS_FoundationStack(x, y, self, suit=i,
440                                                    mod=13, max_cards=26))
441            y += l.YS
442
443        # define stack-groups
444        l.defaultStackGroups()
445
446    def startGame(self):
447        self.startDealSample()
448        self.s.talon.dealRow()
449        for i in (1, 2, 3):
450            self.s.talon.dealRow(rows=self.s.rows[i:-i])
451
452    shallHighlightMatch = Game._shallHighlightMatch_ACW
453
454
455# ************************************************************************
456# * Surprise
457# ************************************************************************
458
459class Surprise_ReserveStack(ReserveStack):
460    def acceptsCards(self, from_stack, cards):
461        if not ReserveStack.acceptsCards(self, from_stack, cards):
462            return False
463        return len(self.game.s.talon.cards) == 0
464
465
466class Surprise(Gypsy):
467
468    def createGame(self, rows=8, reserves=1):
469        # create layout
470        l, s = Layout(self), self.s
471
472        # set window
473        self.setSize(l.XM+11*l.XS, l.YM+2*l.YS+12*l.YOFFSET+20)
474
475        # create stacks
476        x, y = l.XM, l.YM
477        s.talon = self.Talon_Class(x, y, self)
478        l.createText(s.talon, 's')
479        x += l.XS
480        stack = Surprise_ReserveStack(x, y, self, max_cards=3)
481        xoffset = min(l.XOFFSET, l.XS//3)
482        stack.CARD_XOFFSET = xoffset
483        s.reserves.append(stack)
484        x += 2*l.XS
485        for i in range(8):
486            s.foundations.append(SS_FoundationStack(x, y, self, suit=i//2))
487            x += l.XS
488        x, y = l.XM, l.YM+l.YS+l.TEXT_HEIGHT
489        for i in range(11):
490            s.rows.append(KingAC_RowStack(x, y, self))
491            x += l.XS
492
493        # define stack-groups
494        l.defaultStackGroups()
495
496    def startGame(self):
497        for i in range(1, 6):
498            self.s.talon.dealRow(rows=self.s.rows[i:-i], flip=0, frames=0)
499        self._startAndDealRow()
500
501
502# ************************************************************************
503# * Elba
504# ************************************************************************
505
506class Elba(Gypsy):
507    Layout_Method = staticmethod(Layout.klondikeLayout)
508    RowStack_Class = KingAC_RowStack
509
510    def createGame(self):
511        Gypsy.createGame(self, rows=10)
512
513    def startGame(self):
514        for i in range(4):
515            self.s.talon.dealRow(flip=0, frames=0)
516        self._startAndDealRow()
517
518
519# ************************************************************************
520# * Millie
521# ************************************************************************
522
523class Millie(Gypsy):
524    Layout_Method = staticmethod(Layout.klondikeLayout)
525
526    def createGame(self):
527        Gypsy.createGame(self, playcards=24)
528
529    def startGame(self):
530        self._startAndDealRow()
531
532
533# ************************************************************************
534# * Hypotenuse
535# * Eternal Triangle
536# * Right Triangle
537# ************************************************************************
538
539class Hypotenuse(Gypsy):
540    Layout_Method = staticmethod(Layout.klondikeLayout)
541    RowStack_Class = KingAC_RowStack
542
543    def createGame(self):
544        Gypsy.createGame(self, rows=10, playcards=24)
545
546    def startGame(self, flip=0, reverse=1):
547        for i in range(1, 10):
548            self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0)
549        self._startAndDealRow()
550
551
552class EternalTriangle(Hypotenuse):
553
554    def startGame(self, flip=0, reverse=1):
555        for i in range(1, 10):
556            self.s.talon.dealRow(rows=self.s.rows[i:], frames=0)
557        self._startAndDealRow()
558
559
560class RightTriangle_Talon(OpenStack, DealRowTalonStack):
561    def __init__(self, x, y, game, max_rounds=1, num_deal=1, **cap):
562        kwdefault(cap, max_move=1, max_accept=1, max_cards=999999)
563        Stack.__init__(self, x, y, game, cap=cap)
564        self.max_rounds = max_rounds
565        self.num_deal = num_deal
566        self.round = 1
567        self.base_cards = []        # for DealBaseCard_StackMethods
568
569    def clickHandler(self, event):
570        if self.cards and not self.cards[-1].face_up:
571            return self.game.dealCards(sound=True)
572        return OpenStack.clickHandler(self, event)
573
574    def canDealCards(self):
575        if not DealRowTalonStack.canDealCards(self):
576            return False
577        if self.cards and self.cards[-1].face_up:
578            return False
579        return True
580
581    def canFlipCard(self):
582        return False
583
584    getBottomImage = Stack._getReserveBottomImage
585
586    prepareView = Stack.prepareView
587
588    resize = Stack.resize
589
590    def getHelp(self):
591        return DealRowTalonStack.getHelp(self)
592
593
594class RightTriangle(Hypotenuse):
595    Talon_Class = RightTriangle_Talon
596
597    def createGame(self):
598        Gypsy.createGame(self, rows=10, playcards=24)
599        self.sg.dropstacks.append(self.s.talon)
600        self.sg.openstacks.append(self.s.talon)
601        self.sg.reservestacks.append(self.s.talon)
602
603
604# ************************************************************************
605# * Trapdoor
606# ************************************************************************
607
608class Trapdoor_Talon(DealRowTalonStack):
609    def dealCards(self, sound=False):
610        if not self.cards:
611            return 0
612        if sound:
613            self.game.startDealSample()
614        n = 0
615        rows = self.game.s.rows
616        reserves = self.game.s.reserves
617        for i in range(len(rows)):
618            r1 = reserves[i]
619            r2 = rows[i]
620            if r1.cards:
621                r1.moveMove(1, r2)
622                n += 1
623        n += self.dealRowAvail(rows=self.game.s.reserves, sound=False)
624        if sound:
625            self.game.stopSamples()
626        return n
627
628
629class Trapdoor(Gypsy):
630    Foundation_Class = SS_FoundationStack
631    RowStack_Class = AC_RowStack
632
633    def createGame(self, rows=8):
634        kw = {'rows': rows,
635              'waste': 0,
636              'texts': 1,
637              'reserves': rows}
638        Layout(self).createGame(layout_method=Layout.gypsyLayout,
639                                talon_class=Trapdoor_Talon,
640                                foundation_class=self.Foundation_Class,
641                                row_class=self.RowStack_Class,
642                                reserve_class=OpenStack,
643                                **kw
644                                )
645
646    def startGame(self):
647        Gypsy.startGame(self)
648        self.s.talon.dealCards()
649
650
651class TrapdoorSpider(Trapdoor):
652    Foundation_Class = Spider_SS_Foundation
653    RowStack_Class = Spider_RowStack
654    Hint_Class = Spider_Hint
655
656    def createGame(self):
657        Trapdoor.createGame(self, rows=10)
658
659    def startGame(self, flip=0):
660        for i in range(3):
661            self.s.talon.dealRow(flip=flip, frames=0)
662        r = self.s.rows
663        rows = (r[0], r[3], r[6], r[9])
664        self.s.talon.dealRow(rows=rows, flip=flip, frames=0)
665        self.startDealSample()
666        self.s.talon.dealRow()
667        self.s.talon.dealCards()
668
669    shallHighlightMatch = Game._shallHighlightMatch_RK
670    getQuickPlayScore = Game._getSpiderQuickPlayScore
671
672
673# ************************************************************************
674# * Flamenco
675# ************************************************************************
676
677class Flamenco(Gypsy):
678
679    def createGame(self):
680        kw = {'rows': 8,
681              'waste': 0,
682              'texts': 1, }
683        foundation_class = (
684            SS_FoundationStack,
685            StackWrapper(SS_FoundationStack, base_rank=KING, dir=-1))
686        Layout(self).createGame(layout_method=Layout.gypsyLayout,
687                                talon_class=DealRowTalonStack,
688                                foundation_class=foundation_class,
689                                row_class=AC_RowStack,
690                                **kw
691                                )
692
693    def _shuffleHook(self, cards):
694        return self._shuffleHookMoveToTop(
695            cards,
696            lambda c: (c.rank in (ACE, KING) and c.deck == 0,
697                       (c.suit, c.rank)))
698
699    def startGame(self):
700        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
701        self._startDealNumRowsAndDealSingleRow(2)
702
703
704# ************************************************************************
705# * Eclipse
706# ************************************************************************
707
708class Eclipse(Gypsy):
709    Layout_Method = staticmethod(Layout.klondikeLayout)
710    RowStack_Class = SS_RowStack
711
712    def createGame(self):
713        Gypsy.createGame(self, rows=13)
714
715    def startGame(self):
716        self._startDealNumRowsAndDealSingleRow(3)
717
718    shallHighlightMatch = Game._shallHighlightMatch_SS
719
720
721# ************************************************************************
722# * Brazilian Patience
723# ************************************************************************
724
725class BrazilianPatience(Gypsy):
726    Layout_Method = staticmethod(Layout.klondikeLayout)
727    RowStack_Class = KingAC_RowStack
728
729    def createGame(self):
730        Gypsy.createGame(self, rows=10, playcards=22)
731
732    def startGame(self, flip=0, reverse=1):
733        for i in range(1, 10):
734            self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0)
735        self._startAndDealRow()
736
737
738# ************************************************************************
739# * Leprechaun
740# ************************************************************************
741
742class Leprechaun_Reserve(OpenStack):
743    def canFlipCard(self):
744        if not OpenStack.canFlipCard(self):
745            return False
746        i = list(self.game.s.reserves).index(self)
747        return len(self.game.s.foundations[i].cards) != 0
748
749
750class Leprechaun(Game):
751
752    def createGame(self):
753
754        # create layout
755        l, s = Layout(self), self.s
756
757        # set window
758        self.setSize(l.XM+9.5*l.XS, l.YM+3*l.YS+l.TEXT_HEIGHT+12*l.YOFFSET)
759
760        # create stacks
761        x, y = l.XM+1.5*l.XS, l.TEXT_HEIGHT
762        for i in range(8):
763            stack = Leprechaun_Reserve(x, y, self)
764            s.reserves.append(stack)
765            l.createText(stack, 'n')
766            x += l.XS
767
768        x, y = l.XM+1.5*l.XS, l.YS+l.TEXT_HEIGHT
769        for i in range(8):
770            s.foundations.append(SS_FoundationStack(x, y, self, suit=i//2))
771            x += l.XS
772
773        x, y = l.XM+1.5*l.XS, 2*l.YS+l.TEXT_HEIGHT
774        for i in range(8):
775            s.rows.append(AC_RowStack(x, y, self))
776            x += l.XS
777
778        s.talon = DealRowTalonStack(l.XM, l.YM, self)
779        l.createText(s.talon, 's')
780
781        # define stack-groups
782        l.defaultStackGroups()
783
784    def startGame(self):
785        for i in range(4):
786            self.s.talon.dealRow(rows=self.s.reserves, flip=0, frames=0)
787        self.s.talon.dealRow(flip=0, frames=0)
788        self.s.talon.dealRow(flip=0, frames=0)
789        self._startAndDealRow()
790
791    shallHighlightMatch = Game._shallHighlightMatch_AC
792
793
794# ************************************************************************
795# * Locked Cards
796# * Topsy-Turvy Queens
797# ************************************************************************
798
799class LockedCards_Reserve(OpenStack):
800    def canFlipCard(self):
801        if not OpenStack.canFlipCard(self):
802            return False
803        i = list(self.game.s.reserves).index(self)
804        return len(self.game.s.foundations[i].cards) == 13
805
806
807class LockedCards_Foundation(SS_FoundationStack):
808    def acceptsCards(self, from_stack, cards):
809        if not SS_FoundationStack.acceptsCards(self, from_stack, cards):
810            return False
811        if self.cards:
812            # check suit
813            return self.cards[-1].suit == cards[0].suit
814        return True
815
816
817class LockedCards(Game):
818    Foundation_Class = LockedCards_Foundation
819    RowStack_Class = AC_RowStack
820
821    def createGame(self):
822
823        # create layout
824        l, s = Layout(self), self.s
825
826        # set window
827        self.setSize(l.XM+10*l.XS, l.YM+3*l.YS+14*l.YOFFSET)
828
829        # create stacks
830        x, y = l.XM, l.YM
831        for i in range(7):
832            s.reserves.append(LockedCards_Reserve(x, y, self))
833            x += l.XS
834
835        x, y = l.XM, l.YM+l.YS
836        for i in range(8):
837            s.foundations.append(self.Foundation_Class(x, y, self,
838                                 suit=ANY_SUIT, max_move=0))
839            x += l.XS
840
841        x, y = l.XM, l.YM+2*l.YS
842        for i in range(8):
843            s.rows.append(self.RowStack_Class(x, y, self))
844            x += l.XS
845
846        x, y = self.width-l.XS, self.height-l.YS
847        s.talon = WasteTalonStack(x, y, self, max_rounds=3)
848        l.createText(s.talon, 'n')
849        l.createRoundText(s.talon, 'nnn')
850
851        x -= l.XS
852        s.waste = WasteStack(x, y, self)
853        l.createText(s.waste, 'n')
854
855        # define stack-groups
856        l.defaultStackGroups()
857
858    def startGame(self, rows=5):
859        self.s.talon.dealRow(rows=self.s.reserves, flip=0, frames=0)
860        self._startDealNumRowsAndDealRowAndCards(rows-1)
861
862    shallHighlightMatch = Game._shallHighlightMatch_AC
863
864
865class TopsyTurvyQueens(LockedCards):
866    Foundation_Class = StackWrapper(LockedCards_Foundation,
867                                    base_rank=KING, mod=13)
868    RowStack_Class = StackWrapper(SS_RowStack, mod=13)
869
870    def startGame(self):
871        LockedCards.startGame(self, rows=4)
872
873    shallHighlightMatch = Game._shallHighlightMatch_SSW
874
875
876class KingsSecrets(LockedCards):
877    Foundation_Class = StackWrapper(LockedCards_Foundation,
878                                    base_rank=KING, mod=13)
879    RowStack_Class = StackWrapper(SS_RowStack, mod=13, base_rank=11)
880
881    def startGame(self):
882        LockedCards.startGame(self, rows=5)
883
884    shallHighlightMatch = Game._shallHighlightMatch_SSW
885
886
887# ************************************************************************
888# * Thirty
889# ************************************************************************
890
891class Thirty_RowStack(BasicRowStack):
892    def acceptsCards(self, from_stack, cards):
893        if not BasicRowStack.acceptsCards(self, from_stack, cards):
894            return False
895        if self.cards:
896            # check the rank - an ACE equals a Six
897            rank = self.cards[-1].rank
898            if rank == ACE:
899                rank = 5
900            if (rank + self.cap.dir) % self.cap.mod != cards[0].rank:
901                return False
902        return True
903
904    def canMoveCards(self, cards):
905        if not BasicRowStack.canMoveCards(self, cards):
906            return False
907        c1 = cards[0]
908        for c2 in cards[1:]:
909            if c1.suit != c2.suit:
910                return False
911            # check the rank - an ACE equals a Six
912            rank = c1.rank
913            if rank == ACE:
914                rank = 5
915            if (rank + self.cap.dir) % self.cap.mod != c2.rank:
916                return False
917            c1 = c2
918        return True
919
920
921class Thirty(Game):
922    Hint_Class = Spider_Hint
923
924    def createGame(self):
925
926        l, s = Layout(self), self.s
927        self.setSize(l.XM+7*l.XS, l.YM+2*l.YS+12*l.YOFFSET)
928
929        x, y = l.XM, l.YM
930        for i in range(2):
931            s.reserves.append(OpenStack(x, y, self))
932            x += l.XS
933
934        x, y = l.XM+3*l.XS, l.YM
935        for i in range(4):
936            s.foundations.append(DieRussische_Foundation(x, y, self,
937                                                         suit=i, max_cards=8))
938            x += l.XS
939
940        x, y = l.XM+l.XS//2, l.YM+l.YS
941        for i in range(6):
942            s.rows.append(Thirty_RowStack(x, y, self,
943                          max_move=UNLIMITED_MOVES,
944                          max_accept=UNLIMITED_ACCEPTS))
945            x += l.XS
946
947        x, y = self.width-l.XS, self.height-l.YS
948        s.talon = InitialDealTalonStack(x, y, self)
949
950        l.defaultAll()
951
952    def startGame(self):
953        self._startDealNumRows(4)
954        self.s.talon.dealRow()
955        self.s.talon.dealRow(rows=self.s.reserves)
956
957    shallHighlightMatch = Game._shallHighlightMatch_RK
958    getQuickPlayScore = Game._getSpiderQuickPlayScore
959
960
961# register the game
962registerGame(GameInfo(1, Gypsy, "Gypsy",
963                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
964registerGame(GameInfo(65, Giant, "Giant",
965                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
966registerGame(GameInfo(3, Irmgard, "Irmgard",
967                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
968registerGame(GameInfo(119, DieKoenigsbergerin, "Die Koenigsbergerin",
969                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
970registerGame(GameInfo(174, DieRussische, "Russian Patience",
971                      GI.GT_2DECK_TYPE | GI.GT_OPEN, 2, 0, GI.SL_MOSTLY_SKILL,
972                      ranks=(0, 6, 7, 8, 9, 10, 11, 12),
973                      altnames=("Die Russische",)))
974registerGame(GameInfo(62, MissMilligan, "Miss Milligan",
975                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
976registerGame(GameInfo(200, Nomad, "Nomad",
977                      GI.GT_GYPSY | GI.GT_CONTRIB | GI.GT_ORIGINAL, 2, 0,
978                      GI.SL_MOSTLY_SKILL))
979registerGame(GameInfo(78, MilliganCell, "Milligan Cell",
980                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
981registerGame(GameInfo(217, MilliganHarp, "Milligan Harp",
982                      GI.GT_GYPSY, 2, 0, GI.SL_BALANCED))
983registerGame(GameInfo(218, Carlton, "Carlton",
984                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
985registerGame(GameInfo(68, LexingtonHarp, "Lexington Harp",
986                      GI.GT_YUKON, 2, 0, GI.SL_BALANCED))
987registerGame(GameInfo(154, Brunswick, "Brunswick",
988                      GI.GT_YUKON, 2, 0, GI.SL_BALANCED))
989registerGame(GameInfo(121, Mississippi, "Mississippi",
990                      GI.GT_YUKON | GI.GT_XORIGINAL, 2, 0, GI.SL_MOSTLY_SKILL))
991registerGame(GameInfo(122, Griffon, "Griffon",
992                      GI.GT_YUKON | GI.GT_XORIGINAL, 2, 0, GI.SL_MOSTLY_SKILL))
993registerGame(GameInfo(226, Blockade, "Blockade",
994                      GI.GT_GYPSY, 2, 0, GI.SL_BALANCED))
995registerGame(GameInfo(412, Cone, "Cone",
996                      GI.GT_GYPSY, 2, 0, GI.SL_BALANCED))
997registerGame(GameInfo(463, Surprise, "Surprise",
998                      GI.GT_GYPSY, 2, 0, GI.SL_BALANCED))
999registerGame(GameInfo(469, PhantomBlockade, "Phantom Blockade",
1000                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
1001registerGame(GameInfo(478, Elba, "Elba",
1002                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
1003registerGame(GameInfo(486, ImperialGuards, "Imperial Guards",
1004                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
1005registerGame(GameInfo(487, Millie, "Millie",
1006                      GI.GT_GYPSY, 2, 0, GI.SL_BALANCED))
1007registerGame(GameInfo(498, Steve, "Steve",
1008                      GI.GT_GYPSY, 2, 0, GI.SL_BALANCED))
1009registerGame(GameInfo(566, Hypotenuse, "Hypotenuse",
1010                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
1011registerGame(GameInfo(567, EternalTriangle, "Eternal Triangle",
1012                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL,
1013                      altnames=('Lobachevsky',)))
1014registerGame(GameInfo(568, RightTriangle, "Right Triangle",
1015                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
1016registerGame(GameInfo(580, Trapdoor, "Trapdoor",
1017                      GI.GT_GYPSY | GI.GT_ORIGINAL, 2, 0, GI.SL_MOSTLY_SKILL))
1018registerGame(GameInfo(581, Flamenco, "Flamenco",
1019                      GI.GT_GYPSY | GI.GT_ORIGINAL, 2, 0, GI.SL_MOSTLY_SKILL))
1020registerGame(GameInfo(584, Eclipse, "Eclipse",
1021                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
1022registerGame(GameInfo(640, BrazilianPatience, "Brazilian Patience",
1023                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
1024registerGame(GameInfo(666, TrapdoorSpider, "Trapdoor Spider",
1025                      GI.GT_SPIDER | GI.GT_ORIGINAL, 2, 0, GI.SL_MOSTLY_SKILL))
1026registerGame(GameInfo(712, Leprechaun, "Leprechaun",
1027                      GI.GT_GYPSY | GI.GT_ORIGINAL, 2, 0, GI.SL_MOSTLY_SKILL))
1028registerGame(GameInfo(718, LockedCards, "Locked Cards",
1029                      GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED))
1030registerGame(GameInfo(721, Thirty, "Thirty",
1031                      GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL,
1032                      ranks=(0, 6, 7, 8, 9, 10, 11, 12)))
1033registerGame(GameInfo(725, TopsyTurvyQueens, "Topsy-Turvy Queens",
1034                      GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED))
1035registerGame(GameInfo(792, KingsSecrets, "King's Secrets",
1036                      GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED))
1037