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.hint import CautiousDefaultHint
27from pysollib.hint import FreeCellSolverWrapper
28from pysollib.layout import Layout
29from pysollib.mfxutil import kwdefault
30from pysollib.stack import \
31        AC_FoundationStack, \
32        AC_RowStack, \
33        InitialDealTalonStack, \
34        RK_RowStack, \
35        SS_FoundationStack, \
36        SS_RowStack, \
37        StackWrapper, \
38        SuperMoveAC_RowStack, \
39        TalonStack, \
40        UD_AC_RowStack, \
41        UD_SS_RowStack
42from pysollib.util import ACE, KING, NO_RANK, UNLIMITED_ACCEPTS, \
43        UNLIMITED_MOVES
44
45
46# ************************************************************************
47# * Castles in Spain
48# ************************************************************************
49
50class CastlesInSpain(Game):
51    Layout_Method = staticmethod(Layout.bakersDozenLayout)
52    Talon_Class = InitialDealTalonStack
53    Foundation_Class = SS_FoundationStack
54    RowStack_Class = SuperMoveAC_RowStack
55    Hint_Class = CautiousDefaultHint
56    Solver_Class = FreeCellSolverWrapper()
57
58    #
59    # game layout
60    #
61
62    def createGame(self, **layout):
63        # create layout
64        l, s = Layout(self), self.s
65        kwdefault(layout, rows=13, playcards=9)
66        self.Layout_Method(l, **layout)
67        self.setSize(l.size[0], l.size[1])
68        # create stacks
69        s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self)
70        for r in l.s.foundations:
71            s.foundations.append(
72                self.Foundation_Class(r.x, r.y, self, suit=r.suit))
73        for r in l.s.rows:
74            s.rows.append(self.RowStack_Class(r.x, r.y, self))
75        # default
76        l.defaultAll()
77        return l
78
79    def startGame(self, flip=(0, 0, 0)):
80        for f in flip:
81            self.s.talon.dealRow(flip=f, frames=0)
82        self._startAndDealRow()
83
84    shallHighlightMatch = Game._shallHighlightMatch_AC
85
86
87# ************************************************************************
88# * Martha
89# ************************************************************************
90
91class Martha_RowStack(AC_RowStack):
92    def acceptsCards(self, from_stack, cards):
93        if not AC_RowStack.acceptsCards(self, from_stack, cards):
94            return False
95        # when empty, only accept a single card
96        return self.cards or len(cards) == 1
97
98
99class Martha(CastlesInSpain):
100    Solver_Class = None
101    RowStack_Class = Martha_RowStack
102
103    def createGame(self):
104        CastlesInSpain.createGame(self, rows=12, playcards=13)
105
106    def _shuffleHook(self, cards):
107        # move Aces to bottom of the Talon (i.e. last cards to be dealt)
108        return self._shuffleHookMoveToBottom(
109            cards, lambda c: (c.rank == 0, c.suit))
110
111    def startGame(self):
112        CastlesInSpain.startGame(self, flip=(0, 1, 0))
113        self.s.talon.dealRow(rows=self.s.foundations)
114
115
116# ************************************************************************
117# * Baker's Dozen
118# ************************************************************************
119
120class BakersDozen(CastlesInSpain):
121    RowStack_Class = StackWrapper(RK_RowStack, max_move=1, max_accept=1,
122                                  base_rank=NO_RANK)
123    Solver_Class = FreeCellSolverWrapper(preset='bakers_dozen')
124
125    def _shuffleHook(self, cards):
126        # move Kings to bottom of each stack
127        i, n = 0, len(self.s.rows)
128        kings = []
129        for c in cards:
130            if c.rank == KING:
131                kings.append(i)
132            i = i + 1
133        for i in kings:
134            j = i % n
135            while j < i:
136                if cards[j].rank != KING:
137                    cards[i], cards[j] = cards[j], cards[i]
138                    break
139                j = j + n
140        cards.reverse()
141        return cards
142
143    def startGame(self):
144        CastlesInSpain.startGame(self, flip=(1, 1, 1))
145
146    shallHighlightMatch = Game._shallHighlightMatch_RK
147
148
149# ************************************************************************
150# * Spanish Patience
151# * Portuguese Solitaire
152# ************************************************************************
153
154class SpanishPatience(BakersDozen):
155    Foundation_Class = AC_FoundationStack
156    Solver_Class = None
157
158
159class PortugueseSolitaire(BakersDozen):
160    RowStack_Class = StackWrapper(RK_RowStack, base_rank=KING, max_move=1)
161    Solver_Class = FreeCellSolverWrapper(sbb='rank', esf='kings')
162
163    def _shuffleHook(self, cards):
164        return cards
165
166
167class SpanishPatienceII(PortugueseSolitaire):
168    RowStack_Class = StackWrapper(RK_RowStack, max_move=1)
169    Solver_Class = FreeCellSolverWrapper(sbb='rank')
170
171
172# ************************************************************************
173# * Good Measure
174# ************************************************************************
175
176class GoodMeasure(BakersDozen):
177    Solver_Class = FreeCellSolverWrapper(preset='good_measure')
178
179    def createGame(self):
180        CastlesInSpain.createGame(self, rows=10)
181
182    def _shuffleHook(self, cards):
183        cards = BakersDozen._shuffleHook(self, cards)
184        # move 2 Aces to bottom of the Talon (i.e. last cards to be dealt)
185        return self._shuffleHookMoveToBottom(
186            cards, lambda c: (c.rank == 0, c.suit), 2)
187
188    def startGame(self):
189        CastlesInSpain.startGame(self, flip=(1, 1, 1, 1))
190        for i in range(2):
191            c = self.s.talon.cards[-1]
192            assert c.rank == ACE
193            self.flipMove(self.s.talon)
194            self.moveMove(1, self.s.talon, self.s.foundations[c.suit])
195
196
197# ************************************************************************
198# * Cruel
199# * Unusual
200# ************************************************************************
201
202class Cruel_Talon(TalonStack):
203    def canDealCards(self):
204        # FIXME: this is to avoid loops in the demo
205        # if self.game.demo and self.game.moves.index >= 100:
206        #    return False
207        if self.round == self.max_rounds:
208            return False
209        return not self.game.isGameWon()
210
211    def dealCards(self, sound=False):
212        lr = len(self.game.s.rows)
213        # move all cards to the Talon and redeal (no shuffling)
214        num_cards = 0
215        assert len(self.cards) == 0
216        rows = list(self.game.s.rows)[:]
217        rows.reverse()
218        for r in rows:
219            for i in range(len(r.cards)):
220                num_cards = num_cards + 1
221                self.game.moveMove(1, r, self, frames=0)
222        assert len(self.cards) == num_cards
223        if num_cards == 0:          # game already finished
224            return 0
225        # redeal in packs of 4 cards
226        self.game.nextRoundMove(self)
227        n, i = num_cards, 0
228        deal = [4] * lr
229        extra_cards = n - 4 * lr
230        while extra_cards > 0:
231            # note: this can only happen in Tarock games like Nasty
232            deal[i] = deal[i] + 1
233            i = (i + 1) % lr
234            extra_cards = extra_cards - 1
235        # print n, deal
236        self.game.startDealSample()
237        for i in range(lr):
238            k = min(deal[i], n)
239            frames = (0, 4)[n <= 3*4]
240            for j in range(k):
241                self.game.moveMove(1, self, self.game.s.rows[i], frames=frames)
242            n = n - k
243            if n == 0:
244                break
245        # done
246        self.game.stopSamples()
247        assert n == len(self.cards) == 0
248        return num_cards
249
250
251class Cruel(CastlesInSpain):
252    Talon_Class = StackWrapper(Cruel_Talon, max_rounds=-1)
253    RowStack_Class = StackWrapper(SS_RowStack, base_rank=NO_RANK)
254    # Solver_Class = FreeCellSolverWrapper(preset='cruel')
255    Solver_Class = None
256
257    def createGame(self):
258        return CastlesInSpain.createGame(self, rows=12)
259
260    def _shuffleHook(self, cards):
261        # move Aces to bottom of the Talon (i.e. last cards to be dealt)
262        return self._shuffleHookMoveToBottom(
263            cards, lambda c: (c.rank == 0, c.suit))
264
265    def startGame(self):
266        CastlesInSpain.startGame(self, flip=(1, 1, 1))
267        self.s.talon.dealRow(rows=self.s.foundations)
268
269    shallHighlightMatch = Game._shallHighlightMatch_SS
270
271
272class Unusual(Cruel):
273
274    def createGame(self):
275        return CastlesInSpain.createGame(self, rows=24)
276
277
278# ************************************************************************
279# * Royal Family
280# * Indefatigable
281# ************************************************************************
282
283class RoyalFamily(Cruel):
284    Foundation_Class = StackWrapper(SS_FoundationStack, base_rank=KING, dir=-1)
285    Talon_Class = StackWrapper(Cruel_Talon, max_rounds=2)
286    RowStack_Class = UD_AC_RowStack
287
288    def createGame(self):
289        lay = Cruel.createGame(self)
290        lay.createRoundText(self.s.talon, 'sw')
291
292    def _shuffleHook(self, cards):
293        # move Kings to bottom of the Talon (i.e. last cards to be dealt)
294        return self._shuffleHookMoveToBottom(
295            cards, lambda c: (c.rank == KING, c.suit))
296
297    shallHighlightMatch = Game._shallHighlightMatch_AC
298
299
300class Indefatigable(Cruel):
301    Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0)
302    Talon_Class = StackWrapper(Cruel_Talon, max_rounds=3)
303    RowStack_Class = UD_SS_RowStack
304
305    def createGame(self):
306        lay = Cruel.createGame(self)
307        lay.createRoundText(self.s.talon, 'sw')
308
309    def _shuffleHook(self, cards):
310        # move Aces to bottom of the Talon (i.e. last cards to be dealt)
311        return self._shuffleHookMoveToBottom(
312            cards, lambda c: (c.rank == ACE, c.suit))
313
314    shallHighlightMatch = Game._shallHighlightMatch_SS
315
316
317# ************************************************************************
318# * Perseverance
319# ************************************************************************
320
321class Perseverance(Cruel, BakersDozen):
322    Talon_Class = StackWrapper(Cruel_Talon, max_rounds=3)
323    RowStack_Class = StackWrapper(SS_RowStack, base_rank=NO_RANK, dir=-1,
324                                  max_move=UNLIMITED_MOVES,
325                                  max_accept=UNLIMITED_ACCEPTS)
326    Solver_Class = None
327
328    def createGame(self):
329        lay = Cruel.createGame(self)
330        lay.createRoundText(self.s.talon, 'sw')
331
332    def _shuffleHook(self, cards):
333        # move Kings to bottom of each stack (???)
334        # cards = BakersDozen._shuffleHook(self, cards)
335        # move Aces to bottom of the Talon (i.e. last cards to be dealt)
336        cards = Cruel._shuffleHook(self, cards)
337        return cards
338
339#      def dealCards(self, sound=True):
340#          Cruel.dealCards(self, sound)
341
342
343# ************************************************************************
344# * Ripple Fan
345# ************************************************************************
346
347class RippleFan(CastlesInSpain):
348    Solver_Class = None
349
350    def createGame(self):
351        # create layout
352        l, s = Layout(self), self.s
353        Layout.bakersDozenLayout(l, rows=13)
354        self.setSize(l.size[0], l.size[1])
355        # create stacks
356        s.talon = Cruel_Talon(l.s.talon.x, l.s.talon.y, self, max_rounds=-1)
357        for r in l.s.foundations:
358            s.foundations.append(
359                SS_FoundationStack(r.x, r.y, self, suit=r.suit))
360        for r in l.s.rows:
361            s.rows.append(SS_RowStack(r.x, r.y, self, base_rank=NO_RANK))
362        # default
363        l.defaultAll()
364
365    def startGame(self):
366        CastlesInSpain.startGame(self, flip=(1, 1, 1))
367
368    shallHighlightMatch = Game._shallHighlightMatch_SS
369
370
371# register the game
372registerGame(GameInfo(83, CastlesInSpain, "Castles in Spain",
373                      GI.GT_BAKERS_DOZEN, 1, 0, GI.SL_BALANCED))
374registerGame(GameInfo(84, Martha, "Martha",
375                      GI.GT_BAKERS_DOZEN, 1, 0, GI.SL_BALANCED))
376registerGame(GameInfo(31, BakersDozen, "Baker's Dozen",
377                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0,
378                      GI.SL_MOSTLY_SKILL))
379registerGame(GameInfo(85, SpanishPatience, "Spanish Patience",
380                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0,
381                      GI.SL_MOSTLY_SKILL))
382registerGame(GameInfo(86, GoodMeasure, "Good Measure",
383                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0,
384                      GI.SL_MOSTLY_SKILL))
385registerGame(GameInfo(104, Cruel, "Cruel",
386                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, -1, GI.SL_BALANCED))
387registerGame(GameInfo(291, RoyalFamily, "Royal Family",
388                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 1,
389                      GI.SL_MOSTLY_SKILL))
390registerGame(GameInfo(308, PortugueseSolitaire, "Portuguese Solitaire",
391                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0,
392                      GI.SL_MOSTLY_SKILL))
393registerGame(GameInfo(404, Perseverance, "Perseverance",
394                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 2, GI.SL_BALANCED))
395registerGame(GameInfo(369, RippleFan, "Ripple Fan",
396                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, -1,
397                      GI.SL_MOSTLY_SKILL))
398registerGame(GameInfo(515, Indefatigable, "Indefatigable",
399                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 2,
400                      GI.SL_MOSTLY_SKILL))
401registerGame(GameInfo(664, SpanishPatienceII, "Spanish Patience II",
402                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0,
403                      GI.SL_MOSTLY_SKILL))
404registerGame(GameInfo(823, Unusual, "Unusual",
405                      GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 2, -1, GI.SL_BALANCED))
406