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.unionsquare import UnionSquare_Foundation
27from pysollib.hint import CautiousDefaultHint, DefaultHint
28from pysollib.layout import Layout
29from pysollib.mygettext import _
30from pysollib.stack import \
31        AC_FoundationStack, \
32        AC_RowStack, \
33        AbstractFoundationStack, \
34        BasicRowStack, \
35        DealRowTalonStack, \
36        OpenStack, \
37        OpenTalonStack, \
38        RK_FoundationStack, \
39        RK_RowStack, \
40        ReserveStack, \
41        SS_FoundationStack, \
42        SS_RowStack, \
43        Stack, \
44        StackWrapper, \
45        UD_RK_RowStack, \
46        UD_SS_RowStack, \
47        WasteStack, \
48        WasteTalonStack, \
49        isSameSuitSequence
50from pysollib.util import ACE, ANY_RANK, ANY_SUIT, JACK, KING, NO_RANK
51
52# ************************************************************************
53# * Royal Cotillion
54# ************************************************************************
55
56
57class RoyalCotillion_Foundation(SS_FoundationStack):
58    def getBottomImage(self):
59        if self.cap.base_rank == 1:
60            return self.game.app.images.getLetter(1)
61        return self.game.app.images.getSuitBottom(self.cap.base_suit)
62
63
64class RoyalCotillion(Game):
65    Foundation_Class = RoyalCotillion_Foundation
66
67    #
68    # game layout
69    #
70
71    def createGame(self):
72        # create layout
73        l, s = Layout(self), self.s
74
75        # set window
76        self.setSize(l.XM + 10*l.XS, l.YM + 4*l.YS)
77
78        # create stacks
79        for i in range(4):
80            x, y, = l.XM + i*l.XS, l.YM
81            s.rows.append(BasicRowStack(x, y, self, max_accept=0))
82        for i in range(4):
83            x, y, = l.XM + 4*l.XS, l.YM + i*l.YS
84            s.foundations.append(
85                self.Foundation_Class(x, y, self, i, dir=2, mod=13))
86            x += l.XS
87            s.foundations.append(
88                self.Foundation_Class(
89                    x, y, self, i, dir=2, mod=13, base_rank=1))
90        for i in range(4):
91            for j in range(4):
92                x, y, = l.XM + (j+6)*l.XS, l.YM + i*l.YS
93                s.reserves.append(ReserveStack(x, y, self, max_accept=0))
94        x, y = l.XM + l.XS, self.height - l.YS
95        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
96        l.createText(s.talon, "sw")
97        x += l.XS
98        s.waste = WasteStack(x, y, self)
99        l.createText(s.waste, "se")
100
101        # define stack-groups
102        l.defaultStackGroups()
103
104    #
105    # game overrides
106    #
107
108    def startGame(self):
109        self.s.talon.dealRow(rows=self.s.reserves, frames=0)
110        self.startDealSample()
111        for i in range(3):
112            self.s.talon.dealRow()
113        self.s.talon.dealCards()          # deal first card to WasteStack
114
115    def fillStack(self, stack):
116        if not stack.cards:
117            old_state = self.enterState(self.S_FILL)
118            if stack is self.s.waste and self.s.talon.cards:
119                self.s.talon.dealCards()
120            elif stack in self.s.reserves and self.s.waste.cards:
121                self.s.waste.moveMove(1, stack)
122            self.leaveState(old_state)
123
124    def getHighlightPilesStacks(self):
125        return ()
126
127    def getAutoStacks(self, event=None):
128        if event is None:
129            # disable auto drop - this would ruin the whole gameplay
130            return (self.sg.dropstacks, (), self.sg.dropstacks)
131        else:
132            # rightclickHandler
133            return (self.sg.dropstacks, self.sg.dropstacks, self.sg.dropstacks)
134
135
136# ************************************************************************
137# * Odd and Even
138# ************************************************************************
139
140class OddAndEven(RoyalCotillion):
141    def createGame(self):
142        # create layout
143        l, s = Layout(self), self.s
144
145        # set window
146        self.setSize(l.XM + 8*l.XS, l.YM + 4*l.YS)
147
148        # create stacks
149        x, y, = l.XM, l.YM
150        for i in range(4):
151            s.foundations.append(
152                self.Foundation_Class(x, y, self, i, dir=2, mod=13))
153            x += l.XS
154        for i in range(4):
155            s.foundations.append(
156                self.Foundation_Class(
157                    x, y, self, i, dir=2, mod=13, base_rank=1))
158            x += l.XS
159        for i in range(2):
160            x, y, = l.XM + ((4, 3)[i])*l.XS, l.YM + (i+1)*l.YS
161            for j in range((4, 5)[i]):
162                s.reserves.append(ReserveStack(x, y, self, max_accept=0))
163                x += l.XS
164        x, y = l.XM, self.height - l.YS
165        s.talon = WasteTalonStack(x, y, self, max_rounds=2)
166        l.createText(s.talon, "n")
167        l.createRoundText(s.talon, 'nnn')
168        x += l.XS
169        s.waste = WasteStack(x, y, self)
170        l.createText(s.waste, "n")
171
172        # define stack-groups
173        l.defaultStackGroups()
174
175    #
176    # game overrides
177    #
178
179    def startGame(self):
180        self.startDealSample()
181        self.s.talon.dealRow(rows=self.s.reserves)
182        self.s.talon.dealCards()          # deal first card to WasteStack
183
184
185# ************************************************************************
186# * Kingdom
187# ************************************************************************
188
189class Kingdom(RoyalCotillion):
190    Foundation_Class = RK_FoundationStack
191
192    def createGame(self):
193        # create layout
194        l, s = Layout(self), self.s
195
196        # set window
197        self.setSize(l.XM + 8*l.XS, l.YM + 4*l.YS)
198
199        # create stacks
200        x, y, = l.XM, l.YM
201        for i in range(8):
202            s.foundations.append(self.Foundation_Class(x, y, self, ANY_SUIT))
203            x += l.XS
204        x, y, = l.XM, y + l.YS
205        for i in range(8):
206            s.reserves.append(ReserveStack(x, y, self, max_accept=0))
207            x += l.XS
208        x, y = l.XM + 3*l.XS, l.YM + 3*l.YS
209        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
210        l.createText(s.talon, "sw")
211        x += l.XS
212        s.waste = WasteStack(x, y, self)
213        l.createText(s.waste, "se")
214
215        # define stack-groups
216        l.defaultStackGroups()
217
218    #
219    # game overrides
220    #
221
222    def _shuffleHook(self, cards):
223        # move one Ace to top of the Talon (i.e. first card to be dealt)
224        return self._shuffleHookMoveToTop(
225            cards, lambda c: (c.rank == 0, c.suit), 1)
226
227    def startGame(self):
228        self.startDealSample()
229        self.s.talon.dealRow(rows=(self.s.foundations[0],))
230        self.s.talon.dealRow(rows=self.s.reserves)
231        self.s.talon.dealCards()          # deal first card to WasteStack
232
233
234# ************************************************************************
235# * Alhambra
236# * Granada
237# * Reserves
238# * Grant's Reinforcement
239# ************************************************************************
240
241class Alhambra_Hint(CautiousDefaultHint):
242    def _getDropCardScore(self, score, color, r, t, ncards):
243        return 93000, color
244
245
246class Alhambra_RowStack(UD_SS_RowStack):
247    getBottomImage = Stack._getReserveBottomImage
248
249    def getHelp(self):
250        return _('Waste. Build up or down by suit.')
251
252
253class Alhambra_Talon(DealRowTalonStack):
254    def canDealCards(self):
255        r_cards = sum([len(r.cards) for r in self.game.s.rows])
256        if self.cards:
257            return True
258        elif r_cards and self.round != self.max_rounds:
259            return True
260        return False
261
262    def _deal(self):
263        num_cards = 0
264        for r in self.game.s.rows:
265            if self.cards:
266                self.game.flipAndMoveMove(self, r)
267                num_cards += 1
268
269    def dealCards(self, sound=False):
270        old_state = self.game.enterState(self.game.S_DEAL)
271        num_cards = 0
272        rows = self.game.s.rows
273        r_cards = sum([len(r.cards) for r in self.game.s.rows])
274        if self.cards:
275            if sound and not self.game.demo:
276                self.game.playSample("dealwaste")
277            if len(self.game.s.rows) > 1:
278                num_cards = self.dealRowAvail(sound=False, frames=4)
279            else:
280                num_cards = self._deal()
281        elif r_cards and self.round != self.max_rounds:
282            if sound:
283                self.game.playSample("turnwaste", priority=20)
284            for r in rows:
285                for i in range(len(r.cards)):
286                    self.game.moveMove(1, r, self, frames=0)
287                    self.game.flipMove(self)
288            if len(self.game.s.rows) > 1:
289                num_cards = self.dealRowAvail(sound=False, frames=4)
290            else:
291                num_cards = self._deal()
292            self.game.nextRoundMove(self)
293        self.game.leaveState(old_state)
294        return num_cards
295
296
297class Alhambra(Game):
298    Hint_Class = Alhambra_Hint
299
300    RowStack_Class = StackWrapper(Alhambra_RowStack, base_rank=ANY_RANK)
301
302    def createGame(self, rows=1, reserves=8, playcards=3):
303        # create layout
304        l, s = Layout(self), self.s
305
306        # set window
307        w, h = l.XM+8*l.XS, l.YM+3.5*l.YS+playcards*l.YOFFSET
308        h += l.TEXT_HEIGHT
309        self.setSize(w, h)
310
311        # create stacks
312        x, y, = l.XM, l.YM
313        for i in range(4):
314            s.foundations.append(SS_FoundationStack(x, y, self, suit=i,
315                                                    max_move=0))
316            x += l.XS
317        for i in range(4):
318            s.foundations.append(SS_FoundationStack(x, y, self, suit=i,
319                                 max_move=0, base_rank=KING, dir=-1))
320            x += l.XS
321        x, y, = l.XM+(8-reserves)*l.XS//2, y+l.YS
322        for i in range(reserves):
323            stack = OpenStack(x, y, self, max_accept=0)
324            stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET
325            s.reserves.append(stack)
326            x += l.XS
327        x, y = l.XM+(8-1-rows)*l.XS//2, self.height-l.YS
328        s.talon = Alhambra_Talon(x, y, self, max_rounds=3)
329        if rows == 1:
330            l.createText(s.talon, 'sw')
331        else:
332            l.createText(s.talon, 'n')
333        anchor = 'nn'
334        if rows > 1:
335            anchor = 'nnn'
336        l.createRoundText(s.talon, anchor)
337
338        x += l.XS
339        for i in range(rows):
340            stack = self.RowStack_Class(x, y, self, mod=13, max_accept=1)
341            stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0
342            s.rows.append(stack)
343            x += l.XS
344            if rows == 1:
345                l.createText(stack, 'se')
346            else:
347                l.createText(stack, 'n')
348
349        # define stack-groups (non default)
350        l.defaultStackGroups()
351
352    #
353    # game overrides
354    #
355
356    def _shuffleHook(self, cards):
357        # move one Aces and Kings of first deck to top of the Talon (i.e. first
358        # card to be dealt)
359        return self._shuffleHookMoveToTop(
360            cards, lambda c: (c.deck == 0 and
361                              c.rank in (ACE, KING), (c.rank, c.suit)), 8)
362
363    def startGame(self):
364        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
365        for i in range(3):
366            self.s.talon.dealRow(rows=self.s.reserves, frames=0)
367        self.startDealSample()
368        self.s.talon.dealRow(rows=self.s.reserves)
369        self.s.talon.dealCards()
370
371    shallHighlightMatch = Game._shallHighlightMatch_SSW
372
373
374class Granada(Alhambra):
375    def createGame(self):
376        Alhambra.createGame(self, rows=4)
377
378
379class Reserves_RowStack(UD_RK_RowStack):
380    getBottomImage = Stack._getReserveBottomImage
381
382    def getHelp(self):
383        return _('Waste. Build up or down regardless of suit.')
384
385
386class Reserves(Alhambra):
387    RowStack_Class = StackWrapper(Reserves_RowStack, base_rank=NO_RANK)
388
389    def createGame(self):
390        Alhambra.createGame(self, reserves=4, playcards=11)
391
392    def startGame(self):
393        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
394        for i in range(11):
395            self.s.talon.dealRow(rows=self.s.reserves, frames=0)
396        self.startDealSample()
397        self.s.talon.dealRow(rows=self.s.reserves)
398        self.s.talon.dealCards()
399
400    shallHighlightMatch = Game._shallHighlightMatch_RKW
401
402
403class GrantsReinforcement(Reserves):
404    RowStack_Class = StackWrapper(Alhambra_RowStack, base_rank=NO_RANK)
405
406    def fillStack(self, stack):
407        for r in self.s.reserves:
408            if r.cards:
409                continue
410            if self.s.talon.cards:
411                old_state = self.enterState(self.S_FILL)
412                self.s.talon.flipMove()
413                self.s.talon.moveMove(1, r)
414                self.leaveState(old_state)
415
416    shallHighlightMatch = Game._shallHighlightMatch_SSW
417
418
419# ************************************************************************
420# * Carpet
421# ************************************************************************
422
423class Carpet(Game):
424    Foundation_Class = SS_FoundationStack
425
426    #
427    # game layout
428    #
429
430    def createGame(self):
431        # create layout
432        l, s = Layout(self), self.s
433
434        # set window
435        self.setSize(l.XM + 9*l.XS, l.YM + 4*l.YS)
436
437        # create stacks
438        for i in range(4):
439            for j in range(5):
440                x, y = l.XM + (j+3)*l.XS, l.YM + i*l.YS
441                s.rows.append(ReserveStack(x, y, self))
442        for i in range(4):
443            dx, dy = ((2, 1), (8, 1), (2, 2), (8, 2))[i]
444            x, y = l.XM + dx*l.XS, l.YM + dy*l.YS
445            s.foundations.append(self.Foundation_Class(x, y, self, i))
446        x, y = l.XM, l.YM
447        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
448        l.createText(s.talon, "se")
449        y = y + l.YS
450        s.waste = WasteStack(x, y, self)
451        l.createText(s.waste, "se")
452
453        # define stack-groups
454        l.defaultStackGroups()
455
456    #
457    # game overrides
458    #
459
460    def _shuffleHook(self, cards):
461        # move Aces to top of the Talon (i.e. first cards to be dealt)
462        return self._shuffleHookMoveToTop(
463            cards, lambda c: (c.rank == 0, c.suit))
464
465    def startGame(self):
466        self.startDealSample()
467        self.s.talon.dealRow(rows=self.s.foundations)
468        self.s.talon.dealRow()
469        self.s.talon.dealCards()          # deal first card to WasteStack
470
471
472# ************************************************************************
473# * British Constitution
474# ************************************************************************
475
476class BritishConstitution_RowStackMethods:
477    def acceptsCards(self, from_stack, cards):
478        if self in self.game.s.rows[:8] and \
479                from_stack in self.game.s.rows[8:16]:
480            return True
481        if self in self.game.s.rows[8:16] and \
482                from_stack in self.game.s.rows[16:24]:
483            return True
484        if self in self.game.s.rows[16:24] and \
485                from_stack in self.game.s.rows[24:]:
486            return True
487        if self in self.game.s.rows[24:] and from_stack is self.game.s.waste:
488            return True
489        return False
490
491
492class BritishConstitution_RowStack(BritishConstitution_RowStackMethods,
493                                   AC_RowStack):
494    def acceptsCards(self, from_stack, cards):
495        if not AC_RowStack.acceptsCards(self, from_stack, cards):
496            return False
497        return BritishConstitution_RowStackMethods.acceptsCards(
498            self, from_stack, cards)
499
500
501class NewBritishConstitution_RowStack(BritishConstitution_RowStackMethods,
502                                      RK_RowStack):
503    def acceptsCards(self, from_stack, cards):
504        if not RK_RowStack.acceptsCards(self, from_stack, cards):
505            return False
506        return BritishConstitution_RowStackMethods.acceptsCards(
507            self, from_stack, cards)
508
509
510class BritishConstitution_Foundation(SS_FoundationStack):
511    def acceptsCards(self, from_stack, cards):
512        if not SS_FoundationStack.acceptsCards(self, from_stack, cards):
513            return False
514        if from_stack in self.game.s.rows[:8]:
515            return True
516        return False
517
518
519class BritishConstitution(Game):
520    RowStack_Class = BritishConstitution_RowStack
521
522    def createGame(self):
523        # create layout
524        l, s = Layout(self), self.s
525
526        # set window
527        self.setSize(l.XM + 9*l.XS, l.YM + 5*l.YS)
528
529        # create stacks
530        x, y = l.XM+l.XS, l.YM
531        for i in range(8):
532            s.foundations.append(BritishConstitution_Foundation(x, y, self,
533                                 suit=int(i//2), max_cards=11))
534            x += l.XS
535
536        y = l.YM+l.YS
537        for i in range(4):
538            x = l.XM+l.XS
539            for j in range(8):
540                stack = self.RowStack_Class(x, y, self, max_move=1)
541                stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0
542                s.rows.append(stack)
543                x += l.XS
544            y += l.YS
545
546        x, y = l.XM, l.YM
547        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
548        l.createText(s.talon, "s")
549        y += l.YS+l.TEXT_HEIGHT
550        s.waste = WasteStack(x, y, self)
551        l.createText(s.waste, "s")
552
553        # define stack-groups
554        l.defaultStackGroups()
555
556    def startGame(self):
557        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
558        self._startAndDealRowAndCards()
559
560    def _shuffleHook(self, cards):
561        # move Aces to top of the Talon (i.e. first cards to be dealt)
562        return self._shuffleHookMoveToTop(
563            cards, lambda c: (c.rank == ACE, c.suit))
564
565    def fillStack(self, stack):
566        if not stack.cards:
567            if stack in self.s.rows[:24]:
568                return
569            old_state = self.enterState(self.S_FILL)
570            if stack is self.s.waste and self.s.talon.cards:
571                self.s.talon.dealCards()
572            elif stack in self.s.rows[24:] and self.s.waste.cards:
573                self.s.waste.moveMove(1, stack)
574            self.leaveState(old_state)
575
576    shallHighlightMatch = Game._shallHighlightMatch_AC
577
578
579class NewBritishConstitution(BritishConstitution):
580    RowStack_Class = StackWrapper(
581        NewBritishConstitution_RowStack, base_rank=JACK)
582
583    shallHighlightMatch = Game._shallHighlightMatch_RK
584
585
586# ************************************************************************
587# * Twenty
588# ************************************************************************
589
590class Twenty_RowStack(BasicRowStack):
591    def acceptsCards(self, from_stack, cards):
592        if not BasicRowStack.acceptsCards(self, from_stack, cards):
593            return False
594        return len(self.cards) == 0
595
596    def getHelp(self):
597        return _('Tableau. Empty piles can be filled with any card.')
598
599
600class Twenty(Game):
601    def createGame(self):
602        # create layout
603        l, s = Layout(self), self.s
604
605        # set window
606        self.setSize(l.XM+10*l.XS, l.YM+3*l.YS+10*l.YOFFSET)
607
608        # create stacks
609        x, y = l.XM, l.YM
610        s.talon = DealRowTalonStack(x, y, self)
611        l.createText(s.talon, 'se')
612        x += 2*l.XS
613        for i in range(4):
614            s.foundations.append(SS_FoundationStack(x, y, self, suit=i))
615            x += l.XS
616        for i in range(4):
617            s.foundations.append(SS_FoundationStack(x, y, self, suit=i,
618                                 base_rank=KING, dir=-1))
619            x += l.XS
620
621        for y in (l.YM+l.YS, l.YM+2*l.YS+5*l.YOFFSET):
622            x = l.XM
623            for i in range(10):
624                s.rows.append(Twenty_RowStack(x, y, self,
625                              base_rank=ANY_RANK, max_accept=1))
626                x += l.XS
627
628        # define stack-groups
629        l.defaultStackGroups()
630
631    def _shuffleHook(self, cards):
632        return self._shuffleHookMoveToTop(
633            cards,
634            lambda c: (c.rank in (ACE, KING) and c.deck == 1,
635                       (c.rank, c.suit)))
636
637    def startGame(self):
638        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
639        self._startAndDealRow()
640
641    def fillStack(self, stack):
642        if not stack.cards and stack in self.s.rows and self.s.talon.cards:
643            old_state = self.enterState(self.S_FILL)
644            self.flipMove(self.s.talon)
645            self.s.talon.moveMove(1, stack)
646            self.leaveState(old_state)
647
648
649# ************************************************************************
650# * Three Pirates
651# ************************************************************************
652
653class ThreePirates_Talon(DealRowTalonStack):
654    def dealCards(self, sound=False):
655        num_cards = 0
656        old_state = self.game.enterState(self.game.S_DEAL)
657        if self.cards:
658            if sound and not self.game.demo:
659                self.game.playSample("dealwaste")
660            num_cards = self.dealRowAvail(rows=self.game.s.reserves,
661                                          sound=False, frames=4)
662        self.game.leaveState(old_state)
663        return num_cards
664
665
666class ThreePirates(Game):
667    Hint_Class = CautiousDefaultHint
668
669    def createGame(self):
670        l, s = Layout(self), self.s
671
672        self.setSize(l.XM+10*l.XS, l.YM+3*l.YS+16*l.YOFFSET)
673
674        x, y, = l.XM+l.XS, l.YM
675        for i in range(8):
676            s.foundations.append(SS_FoundationStack(x, y, self, suit=i//2))
677            x += l.XS
678
679        x, y, = l.XM, l.YM+l.YS
680        for i in range(10):
681            s.rows.append(SS_RowStack(x, y, self, max_move=1))
682            x += l.XS
683
684        x, y = l.XM, self.height-l.YS
685        s.talon = ThreePirates_Talon(x, y, self)
686        l.createText(s.talon, 'n')
687        x += l.XS
688        for i in (0, 1, 2):
689            stack = WasteStack(x, y, self)
690            s.reserves.append(stack)
691            l.createText(stack, 'n')
692            x += l.XS
693
694        l.defaultStackGroups()
695
696    def startGame(self):
697        self._startDealNumRowsAndDealRowAndCards(3)
698
699    shallHighlightMatch = Game._shallHighlightMatch_SS
700
701
702# ************************************************************************
703# * Frames
704# ************************************************************************
705
706class Frames_Hint(CautiousDefaultHint):
707    def computeHints(self):
708        CautiousDefaultHint.computeHints(self)
709        if self.hints:
710            return
711        if not self.game.s.talon.cards:
712            return
713        for s in self.game.s.reserves:
714            if s.cards:
715                for r in self.game.s.rows:
716                    if r.acceptsCards(s, s.cards):
717                        self.addHint(5000, 1, s, r)
718
719
720class Frames_Foundation(UnionSquare_Foundation):
721    def acceptsCards(self, from_stack, cards):
722        if not UnionSquare_Foundation.acceptsCards(self, from_stack, cards):
723            return False
724        return from_stack in self.game.s.rows
725
726
727class Frames_RowStack(UD_SS_RowStack):
728    def acceptsCards(self, from_stack, cards):
729        if not UD_SS_RowStack.acceptsCards(self, from_stack, cards):
730            return False
731        if not (from_stack in self.game.s.reserves or
732                from_stack in self.game.s.rows):
733            return False
734        if len(self.cards) > 1:
735            cs = self.cards+cards
736            if not (isSameSuitSequence(cs, dir=1) or
737                    isSameSuitSequence(cs, dir=-1)):
738                return False
739        if from_stack in self.game.s.reserves:
740            if hasattr(self.cap, 'column') and \
741                   self.cap.column != from_stack.cap.column:
742                return False
743            if hasattr(self.cap, 'row') and \
744                    self.cap.row != from_stack.cap.row:
745                return False
746        return True
747
748
749class Frames(Game):
750    Hint_Class = Frames_Hint  # CautiousDefaultHint
751
752    def createGame(self):
753        l, s = Layout(self), self.s
754
755        self.setSize(l.XM+8*l.XS, l.YM+5*l.YS)
756
757        x0, y0 = l.XM+2*l.XS, l.YM
758        # foundations (corners)
759        suit = 0
760        for i,  j in ((0, 0), (5, 0), (0, 4), (5, 4)):
761            x, y = x0+i*l.XS, y0+j*l.YS
762            s.foundations.append(Frames_Foundation(x, y, self,
763                                 suit=suit, dir=0, max_cards=26))
764            suit += 1
765        # rows (frame)
766        for i in (1, 2, 3, 4):
767            for j in (0, 4):
768                x, y = x0+i*l.XS, y0+j*l.YS
769                stack = Frames_RowStack(x, y, self)
770                s.rows.append(stack)
771                stack.cap.addattr(column=i)
772                stack.CARD_YOFFSET = 0
773        for i in (0, 5):
774            for j in (1, 2, 3):
775                x, y = x0+i*l.XS, y0+j*l.YS
776                stack = Frames_RowStack(x, y, self)
777                s.rows.append(stack)
778                stack.cap.addattr(row=j)
779                stack.CARD_YOFFSET = 0
780        # reserves (picture)
781        for j in (1, 2, 3):
782            for i in (1, 2, 3, 4):
783                x, y = x0+i*l.XS, y0+j*l.YS
784                stack = OpenStack(x, y, self)
785                s.reserves.append(stack)
786                stack.cap.addattr(column=i)
787                stack.cap.addattr(row=j)
788        # talon & waste
789        x, y, = l.XM, l.YM
790        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
791        l.createText(s.talon, 'ne')
792        y += l.YS
793        s.waste = WasteStack(x, y, self)
794        l.createText(s.waste, 'ne')
795
796        l.defaultStackGroups()
797
798    def startGame(self):
799        self.s.talon.dealRow(frames=0)
800        self.startDealSample()
801        self.s.talon.dealRow(rows=self.s.reserves)
802        self.s.talon.dealCards()
803
804    def fillStack(self, stack):
805        if not stack.cards and stack in self.s.reserves:
806            if not self.s.waste.cards:
807                self.s.talon.dealCards()
808            if self.s.waste.cards:
809                old_state = self.enterState(self.S_FILL)
810                self.s.waste.moveMove(1, stack)
811                self.leaveState(old_state)
812
813    shallHighlightMatch = Game._shallHighlightMatch_SS
814
815
816# ************************************************************************
817# * Royal Rendezvous
818# ************************************************************************
819
820class RoyalRendezvous(Game):
821
822    def createGame(self):
823        l, s = Layout(self), self.s
824        self.setSize(l.XM+9.5*l.XS, l.YM+4.5*l.YS)
825
826        y = l.YM
827        # kings
828        suit = 0
829        for i in (0, 1, 6, 7):
830            x = l.XM+(1.5+i)*l.XS
831            s.foundations.append(SS_FoundationStack(x, y, self, suit=suit,
832                                 base_rank=KING, max_cards=1))
833            suit += 1
834        # aces
835        suit = 0
836        for i in (2, 3, 4, 5):
837            x = l.XM+(1.5+i)*l.XS
838            s.foundations.append(SS_FoundationStack(x, y, self, suit=suit))
839            suit += 1
840        y += l.YS
841        # twos
842        suit = 0
843        for i in (0, 1, 6, 7):
844            x = l.XM+(1.5+i)*l.XS
845            s.foundations.append(SS_FoundationStack(x, y, self, suit=suit,
846                                 base_rank=1, dir=2, max_cards=6))
847            suit += 1
848        # aces
849        suit = 0
850        for i in (2, 3, 4, 5):
851            x = l.XM+(1.5+i)*l.XS
852            s.foundations.append(SS_FoundationStack(x, y, self, suit=suit,
853                                 dir=2, max_cards=6))
854            suit += 1
855
856        y += 1.5*l.YS
857        for i in (0, 1):
858            x = l.XM+1.5*l.XS
859            for j in range(8):
860                s.rows.append(OpenStack(x, y, self, max_accept=0))
861                x += l.XS
862            y += l.YS
863
864        x, y = l.XM, l.YM
865        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
866        l.createText(s.talon, 'ne')
867        y += l.YS
868        s.waste = WasteStack(x, y, self)
869        l.createText(s.waste, 'ne')
870
871        l.defaultStackGroups()
872
873    def _shuffleHook(self, cards):
874        # move twos to top
875        cards = self._shuffleHookMoveToTop(
876            cards,
877            lambda c: (c.rank == 1 and c.deck == 0, c.suit))
878        # move aces to top
879        cards = self._shuffleHookMoveToTop(
880            cards,
881            lambda c: (c.rank == ACE, (c.deck, c.suit)))
882        return cards
883
884    def startGame(self):
885        # deal aces
886        self.s.talon.dealRow(rows=self.s.foundations[4:8], frames=0)
887        self.s.talon.dealRow(rows=self.s.foundations[12:16], frames=0)
888        # deal twos
889        self.s.talon.dealRow(rows=self.s.foundations[8:12], frames=0)
890        #
891        self.startDealSample()
892        self.s.talon.dealRow()
893        self.s.talon.dealCards()
894
895    def fillStack(self, stack):
896        if not stack.cards and stack in self.s.rows:
897            if not self.s.waste.cards:
898                self.s.talon.dealCards()
899            if self.s.waste.cards:
900                old_state = self.enterState(self.S_FILL)
901                self.s.waste.moveMove(1, stack)
902                self.leaveState(old_state)
903
904
905# ************************************************************************
906# * Shady Lanes
907# ************************************************************************
908
909class ShadyLanes_Hint(CautiousDefaultHint):
910    def computeHints(self):
911        CautiousDefaultHint.computeHints(self)
912        if self.hints:
913            return
914        for r in self.game.s.rows:
915            if not r.cards:
916                for s in self.game.s.reserves:
917                    if s.cards:
918                        self.addHint(5000-s.cards[0].rank, 1, s, r)
919
920
921class ShadyLanes_Foundation(AbstractFoundationStack):
922    def acceptsCards(self, from_stack, cards):
923        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
924            return False
925        if self.cards:
926            # check the rank
927            if self.cards[-1].rank+1 != cards[0].rank:
928                return False
929        return True
930
931    def getHelp(self):
932        return _('Foundation. Build up by color.')
933
934
935class ShadyLanes_RowStack(AC_RowStack):
936    def acceptsCards(self, from_stack, cards):
937        if not AC_RowStack.acceptsCards(self, from_stack, cards):
938            return False
939        if not self.cards:
940            return from_stack in self.game.s.reserves
941        return True
942
943
944class ShadyLanes(Game):
945    Hint_Class = ShadyLanes_Hint
946
947    def createGame(self):
948        l, s = Layout(self), self.s
949        self.setSize(l.XM+8*l.XS, l.YM+5*l.YS)
950
951        x, y = l.XM, l.YM
952        for i in range(8):
953            suit = i//2
954            color = suit//2
955            s.foundations.append(
956                ShadyLanes_Foundation(
957                    x, y, self,
958                    base_suit=suit, suit=ANY_SUIT, color=color))
959            x += l.XS
960        x, y = l.XM, l.YM+l.YS
961        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
962        l.createText(s.talon, 'ne')
963        y += l.YS
964        s.waste = WasteStack(x, y, self)
965        l.createText(s.waste, 'ne')
966        x, y = l.XM+2*l.XS, l.YM+l.YS
967        for i in range(4):
968            s.rows.append(ShadyLanes_RowStack(x, y, self, max_move=1))
969            x += l.XS
970
971        x, y = self.width-l.XS, l.YM+l.YS
972        for i in range(4):
973            s.reserves.append(OpenStack(x, y, self, max_accept=0))
974            y += l.YS
975
976        l.defaultStackGroups()
977
978    def fillStack(self, stack):
979        if not stack.cards and stack in self.s.reserves:
980            if not self.s.waste.cards:
981                self.s.talon.dealCards()
982            if self.s.waste.cards:
983                old_state = self.enterState(self.S_FILL)
984                self.s.waste.moveMove(1, stack)
985                self.leaveState(old_state)
986
987    def startGame(self):
988        self.startDealSample()
989        self.s.talon.dealRow(rows=self.s.reserves)
990        self.s.talon.dealRow()
991        self.s.talon.dealCards()
992
993    shallHighlightMatch = Game._shallHighlightMatch_AC
994
995
996# ************************************************************************
997# * Four Winds
998# * Boxing the Compass
999# ************************************************************************
1000
1001class FourWinds(Game):
1002
1003    def createGame(self):
1004        l, s = Layout(self), self.s
1005        self.setSize(l.XM+9*l.XS, l.YM+6*l.YS)
1006
1007        # vertical rows
1008        x = l.XM+l.XS
1009        for i in (0, 1):
1010            y = l.YM+l.YS
1011            for j in range(4):
1012                stack = ReserveStack(x, y, self, base_suit=i)
1013                stack.getBottomImage = stack._getSuitBottomImage
1014                s.rows.append(stack)
1015                y += l.YS
1016            x += 6*l.XS
1017        # horizontal rows
1018        y = l.YM+l.YS
1019        for i in (2, 3):
1020            x = l.XM+2.5*l.XS
1021            for j in range(4):
1022                stack = ReserveStack(x, y, self, base_suit=i)
1023                stack.getBottomImage = stack._getSuitBottomImage
1024                s.rows.append(stack)
1025                x += l.XS
1026            y += 3*l.YS
1027        # foundations
1028        decks = self.gameinfo.decks
1029        for k in range(decks):
1030            suit = 0
1031            for i, j in ((0, 3-decks*0.5+k),
1032                         (8, 3-decks*0.5+k),
1033                         (4.5-decks*0.5+k, 0),
1034                         (4.5-decks*0.5+k, 5)):
1035                x, y = l.XM+i*l.XS, l.YM+j*l.YS
1036                s.foundations.append(SS_FoundationStack(x, y, self,
1037                                     suit=suit, max_move=0))
1038                suit += 1
1039        # talon & waste
1040        x, y = l.XM+3.5*l.XS, l.YM+2.5*l.YS
1041        s.talon = WasteTalonStack(x, y, self, max_rounds=2)
1042        l.createText(s.talon, 's')
1043        l.createRoundText(self.s.talon, 'nn')
1044        x += l.XS
1045        s.waste = WasteStack(x, y, self)
1046        l.createText(s.waste, 's')
1047
1048        l.defaultStackGroups()
1049
1050    def startGame(self):
1051        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
1052        self.startDealSample()
1053        self.s.talon.dealRow()
1054        self.s.talon.dealCards()
1055
1056    def _shuffleHook(self, cards):
1057        # move Aces to top of the Talon (i.e. first cards to be dealt)
1058        return self._shuffleHookMoveToTop(
1059            cards,
1060            lambda c: (c.rank == ACE, (c.deck, c.suit)))
1061
1062
1063class BoxingTheCompass(FourWinds):
1064    pass
1065
1066
1067# ************************************************************************
1068# * Colonel
1069# ************************************************************************
1070
1071class Colonel_Hint(DefaultHint):
1072    def _getMoveCardBonus(self, r, t, pile, rpile):
1073        if r in self.game.s.rows and t in self.game.s.rows:
1074            if rpile:
1075                return 0
1076        return DefaultHint._getMoveCardBonus(self, r, t, pile, rpile)
1077
1078
1079class Colonel_RowStack(SS_RowStack):
1080
1081    def _getStackIndex(self, stack):
1082        index = list(self.game.s.rows).index(stack)
1083        if index < 12:
1084            row = 0
1085        elif 12 <= index < 24:
1086            row = 1
1087        else:
1088            row = 2
1089        return index, row
1090
1091    def acceptsCards(self, from_stack, cards):
1092        if not SS_RowStack.acceptsCards(self, from_stack, cards):
1093            return False
1094
1095        self_index, self_row = self._getStackIndex(self)
1096
1097        if self_row in (1, 2):
1098            above_stack = self.game.s.rows[self_index-12]
1099            if not above_stack.cards:
1100                return False
1101
1102        below_stack = None
1103        if self_row in (0, 1):
1104            below_stack = self.game.s.rows[self_index+12]
1105
1106        # from_stack is waste
1107        if from_stack is self.game.s.waste:
1108            if below_stack is None or not below_stack.cards:
1109                return True
1110            else:
1111                return False
1112
1113        #  from_stack in rows
1114        from_index, from_row = self._getStackIndex(from_stack)
1115        if below_stack and below_stack.cards:
1116            return from_stack is below_stack
1117        return from_row > self_row
1118
1119    def canMoveCards(self, cards):
1120        self_index, self_row = self._getStackIndex(self)
1121        if self_row in (0, 1):
1122            below_stack = self.game.s.rows[self_index+12]
1123            if below_stack.cards:
1124                return False
1125        return SS_RowStack.canMoveCards(self, cards)
1126
1127    getBottomImage = Stack._getReserveBottomImage
1128
1129
1130class Colonel(Game):
1131    Hint_Class = Colonel_Hint
1132
1133    def createGame(self):
1134        l, s = Layout(self), self.s
1135        self.setSize(l.XM+12*l.XS, l.YM+5*l.YS)
1136
1137        x, y = l.XM+2*l.XS, l.YM
1138        for i in range(8):
1139            s.foundations.append(SS_FoundationStack(x, y, self, suit=i//2,
1140                                                    max_move=0))
1141            x += l.XS
1142
1143        y = l.YM+l.YS
1144        for i in range(3):
1145            x = l.XM
1146            for j in range(12):
1147                stack = Colonel_RowStack(x, y, self, max_move=1)
1148                stack.CARD_YOFFSET = 0
1149                s.rows.append(stack)
1150                x += l.XS
1151            y += l.YS
1152
1153        x, y = l.XM+5*l.XS, l.YM+4*l.YS
1154        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
1155        l.createText(s.talon, 'sw')
1156        x += l.XS
1157        s.waste = WasteStack(x, y, self)
1158        l.createText(s.waste, 'se')
1159
1160        l.defaultStackGroups()
1161
1162    def startGame(self):
1163        self.startDealSample()
1164        self.s.talon.dealRow(frames=4)
1165        self.s.talon.dealCards()
1166
1167    shallHighlightMatch = Game._shallHighlightMatch_SS
1168
1169
1170# ************************************************************************
1171# * The Red and the Black
1172# ************************************************************************
1173
1174
1175class TheRedAndTheBlack_Foundation(AC_FoundationStack):
1176    def acceptsCards(self, from_stack, cards):
1177        if not AC_FoundationStack.acceptsCards(self, from_stack, cards):
1178            return False
1179        if from_stack is self.game.s.waste or from_stack in self.game.s.rows:
1180            return True
1181        return False
1182
1183
1184class TheRedAndTheBlack_Reserve(ReserveStack):
1185    def acceptsCards(self, from_stack, cards):
1186        if not ReserveStack.acceptsCards(self, from_stack, cards):
1187            return False
1188        if from_stack is self.game.s.waste:
1189            return True
1190        return False
1191
1192
1193class TheRedAndTheBlack(Game):
1194    Hint_Class = CautiousDefaultHint
1195
1196    def createGame(self):
1197
1198        l, s = Layout(self), self.s
1199        self.setSize(l.XM + 8*l.XS, l.YM + 4.5*l.YS)
1200
1201        x, y = l.XM, l.YM
1202        for i in range(8):
1203            s.foundations.append(TheRedAndTheBlack_Foundation(x, y, self,
1204                                                              suit=i//2))
1205            x += l.XS
1206        x, y = l.XM+2*l.XS, l.YM+l.YS
1207        for i in range(4):
1208            stack = AC_RowStack(x, y, self, max_move=1)
1209            stack.getBottomImage = stack._getReserveBottomImage
1210            stack.CARD_YOFFSET = 0
1211            s.rows.append(stack)
1212            x += l.XS
1213        x, y = l.XM+2*l.XS, l.YM+2*l.YS
1214        for i in range(4):
1215            s.reserves.append(TheRedAndTheBlack_Reserve(x, y, self))
1216            x += l.XS
1217        x, y = l.XM+3*l.XS, l.YM+3.5*l.YS
1218        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
1219        l.createText(s.talon, "sw")
1220        x += l.XS
1221        s.waste = WasteStack(x, y, self)
1222        l.createText(s.waste, "se")
1223
1224        # define stack-groups
1225        l.defaultStackGroups()
1226
1227    def startGame(self):
1228        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
1229        self.startDealSample()
1230        self.s.talon.dealRow()
1231        self.s.talon.dealRow(rows=self.s.reserves)
1232        self.s.talon.dealCards()       # deal first card to WasteStack
1233
1234    def _shuffleHook(self, cards):
1235        # move Aces to top of the Talon (i.e. first cards to be dealt)
1236        return self._shuffleHookMoveToTop(
1237            cards, lambda c: (c.rank == ACE, c.suit))
1238
1239    shallHighlightMatch = Game._shallHighlightMatch_AC
1240
1241
1242# ************************************************************************
1243# * Twilight Zone
1244# ************************************************************************
1245
1246class TwilightZone_Foundation(AC_FoundationStack):
1247    def acceptsCards(self, from_stack, cards):
1248        if not AC_FoundationStack.acceptsCards(self, from_stack, cards):
1249            return False
1250        if from_stack is self.game.s.waste or \
1251                from_stack in self.game.s.reserves:
1252            return False
1253        return True
1254
1255
1256class TwilightZone_Talon(OpenTalonStack, WasteTalonStack):
1257    rightclickHandler = OpenStack.rightclickHandler
1258    doubleclickHandler = OpenStack.doubleclickHandler
1259
1260    def prepareStack(self):
1261        OpenTalonStack.prepareStack(self)
1262        self.waste = self.game.s.waste
1263
1264    canDealCards = WasteTalonStack.canDealCards
1265    dealCards = WasteTalonStack.dealCards
1266
1267
1268class TwilightZone_RowStack(AC_RowStack):
1269    def acceptsCards(self, from_stack, cards):
1270        if not AC_RowStack.acceptsCards(self, from_stack, cards):
1271            return False
1272        if from_stack is self.game.s.waste:
1273            return False
1274        return True
1275
1276
1277class TwilightZone_Waste(WasteStack):
1278    def acceptsCards(self, from_stack, cards):
1279        if not WasteStack.acceptsCards(self, from_stack, cards):
1280            return False
1281        return from_stack is self.game.s.talon
1282
1283
1284class TwilightZone(Game):
1285    Hint_Class = CautiousDefaultHint
1286
1287    def createGame(self):
1288
1289        # create layout
1290        l, s = Layout(self), self.s
1291
1292        # set window
1293        self.setSize(l.XM+7*l.XS, l.YM+5*l.YS)
1294
1295        # create stacks
1296        y = l.YM
1297        for i in range(2):
1298            x = l.XM+3*l.XS
1299            for j in range(4):
1300                s.foundations.append(TwilightZone_Foundation(x, y, self,
1301                                                             suit=j))
1302                x += l.XS
1303            y += l.YS
1304
1305        x, y = l.XM+3*l.XS, l.YM+2*l.YS
1306        for i in range(4):
1307            stack = TwilightZone_RowStack(x, y, self, max_move=1)
1308            stack.CARD_YOFFSET = 0
1309            s.rows.append(stack)
1310            x += l.XS
1311
1312        x, y = l.XM+3*l.XS, l.YM+4*l.YS
1313        for i in range(4):
1314            s.reserves.append(OpenStack(x, y, self))
1315            x += l.XS
1316
1317        x, y = l.XM, l.YM+l.YS//2
1318        s.talon = TwilightZone_Talon(x, y, self, max_move=1, max_rounds=2)
1319        l.createText(s.talon, 's')
1320        l.createRoundText(s.talon, 'nn')
1321        x += l.XS
1322        s.waste = TwilightZone_Waste(x, y, self, max_accept=1)
1323        l.createText(s.waste, 's')
1324
1325        # define stack-groups
1326        l.defaultStackGroups()
1327        self.sg.dropstacks.append(s.talon)
1328        self.sg.openstacks.append(s.waste)
1329
1330    def startGame(self):
1331        self.startDealSample()
1332        self.s.talon.dealRow()
1333        self.s.talon.dealRow(rows=self.s.reserves)
1334        self.s.talon.fillStack()
1335
1336    def fillStack(self, stack):
1337        if not stack.cards:
1338            old_state = self.enterState(self.S_FILL)
1339            if stack in self.s.rows:
1340                i = list(self.s.rows).index(stack)
1341                from_stack = self.s.reserves[i]
1342                if from_stack.cards:
1343                    from_stack.moveMove(1, stack)
1344            elif stack in self.s.reserves:
1345                from_stack = self.s.waste
1346                if not from_stack.cards:
1347                    from_stack = self.s.talon
1348                if from_stack.cards:
1349                    from_stack.moveMove(1, stack)
1350            self.leaveState(old_state)
1351
1352    def _autoDeal(self, sound=True):
1353        return 0
1354
1355    shallHighlightMatch = Game._shallHighlightMatch_AC
1356
1357
1358# register the game
1359registerGame(GameInfo(54, RoyalCotillion, "Royal Cotillion",
1360                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_LUCK))
1361registerGame(GameInfo(55, OddAndEven, "Odd and Even",
1362                      GI.GT_2DECK_TYPE, 2, 1, GI.SL_LUCK))
1363registerGame(GameInfo(143, Kingdom, "Kingdom",
1364                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_MOSTLY_LUCK))
1365registerGame(GameInfo(234, Alhambra, "Alhambra",
1366                      GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED))
1367registerGame(GameInfo(97, Carpet, "Carpet",
1368                      GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
1369registerGame(GameInfo(391, BritishConstitution, "British Constitution",
1370                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED,
1371                      ranks=list(range(11)),  # without Queens and Kings
1372                      altnames=("Constitution",)))
1373registerGame(GameInfo(392, NewBritishConstitution, "New British Constitution",
1374                      GI.GT_2DECK_TYPE | GI.GT_ORIGINAL, 2, 0, GI.SL_BALANCED,
1375                      ranks=list(range(11))  # without Queens and Kings
1376                      ))
1377registerGame(GameInfo(443, Twenty, "Twenty",
1378                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED))
1379registerGame(GameInfo(465, Granada, "Granada",
1380                      GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED))
1381registerGame(GameInfo(579, ThreePirates, "Three Pirates",
1382                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_MOSTLY_SKILL))
1383registerGame(GameInfo(608, Frames, "Frames",
1384                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_MOSTLY_SKILL))
1385registerGame(GameInfo(609, GrantsReinforcement, "Grant's Reinforcement",
1386                      GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED))
1387registerGame(GameInfo(638, RoyalRendezvous, "Royal Rendezvous",
1388                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED))
1389registerGame(GameInfo(639, ShadyLanes, "Shady Lanes",
1390                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED))
1391registerGame(GameInfo(675, FourWinds, "Four Winds",
1392                      GI.GT_1DECK_TYPE, 1, 1, GI.SL_BALANCED))
1393registerGame(GameInfo(676, BoxingTheCompass, "Boxing the Compass",
1394                      GI.GT_2DECK_TYPE, 2, 1, GI.SL_BALANCED))
1395registerGame(GameInfo(693, Colonel, "Colonel",
1396                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_MOSTLY_SKILL))
1397registerGame(GameInfo(695, TheRedAndTheBlack, "The Red and the Black",
1398                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED))
1399registerGame(GameInfo(748, TwilightZone, "Twilight Zone",
1400                      GI.GT_2DECK_TYPE, 2, 1, GI.SL_BALANCED))
1401registerGame(GameInfo(752, Reserves, "Reserves",
1402                      GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED))
1403