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