1from pysollib.game import Game 2from pysollib.gamedb import GI, GameInfo, registerGame 3from pysollib.hint import DefaultHint 4from pysollib.layout import Layout 5from pysollib.mygettext import _ 6from pysollib.pysoltk import MfxCanvasText, get_text_width 7from pysollib.stack import \ 8 BasicRowStack, \ 9 DealRowTalonStack, \ 10 InitialDealTalonStack, \ 11 RK_FoundationStack, \ 12 Stack, \ 13 StackWrapper, \ 14 WasteStack, \ 15 WasteTalonStack 16from pysollib.util import ANY_SUIT, KING, RANKS 17 18 19class Calculation_Hint(DefaultHint): 20 # FIXME: demo logic is a complete nonsense 21 def _getMoveWasteScore(self, score, color, r, t, pile, rpile): 22 assert r is self.game.s.waste and len(pile) == 1 23 score = 30000 24 if len(t.cards) == 0: 25 score = score - (KING - r.cards[0].rank) * 1000 26 elif t.cards[-1].rank < r.cards[0].rank: 27 score = 10000 + t.cards[-1].rank - len(t.cards) 28 elif t.cards[-1].rank == r.cards[0].rank: 29 score = 20000 30 else: 31 score = score - (t.cards[-1].rank - r.cards[0].rank) * 1000 32 return score, color 33 34 35# ************************************************************************ 36# * 37# ************************************************************************ 38 39class BetsyRoss_Foundation(RK_FoundationStack): 40 def updateText(self, update_empty=True): 41 if self.game.preview > 1: 42 return 43 if self.texts.misc: 44 if len(self.cards) == 0: 45 if update_empty: 46 rank = self.cap.base_rank 47 self.texts.misc.config(text=RANKS[rank]) 48 else: 49 self.texts.misc.config(text="") 50 elif len(self.cards) == self.cap.max_cards: 51 self.texts.misc.config(text="") 52 else: 53 rank = (self.cards[-1].rank + self.cap.dir) % self.cap.mod 54 self.texts.misc.config(text=RANKS[rank]) 55 56 57class Calculation_Foundation(BetsyRoss_Foundation): 58 getBottomImage = Stack._getLetterImage 59 60 61class Calculation_RowStack(BasicRowStack): 62 def acceptsCards(self, from_stack, cards): 63 if not BasicRowStack.acceptsCards(self, from_stack, cards): 64 return False 65 # this stack accepts any one card from the Waste pile 66 return from_stack is self.game.s.waste and len(cards) == 1 67 68 getBottomImage = Stack._getReserveBottomImage 69 70 def getHelp(self): 71 return _('Tableau. Build regardless of rank and suit.') 72 73 74# ************************************************************************ 75# * Calculation 76# ************************************************************************ 77 78class Calculation(Game): 79 Hint_Class = Calculation_Hint 80 Foundation_Class = Calculation_Foundation 81 RowStack_Class = StackWrapper( 82 Calculation_RowStack, max_move=1, max_accept=1) 83 84 # 85 # game layout 86 # 87 88 def _getHelpText(self): 89 help = (_('''\ 901: 2 3 4 5 6 7 8 9 T J Q K 912: 4 6 8 T Q A 3 5 7 9 J K 923: 6 9 Q 2 5 8 J A 4 7 T K 934: 8 Q 3 7 J 2 6 T A 5 9 K''')) 94 # calculate text_width 95 lines = help.split('\n') 96 lines.sort(key=len) 97 max_line = lines[-1] 98 text_width = get_text_width(max_line, 99 font=self.app.getFont("canvas_fixed")) 100 return help, text_width 101 102 def createGame(self): 103 104 # create layout 105 l, s = Layout(self, TEXT_HEIGHT=40), self.s 106 help, text_width = self._getHelpText() 107 text_width += 2*l.XM 108 109 # set window 110 w = l.XM+5.5*l.XS+text_width 111 h = max(2*l.YS, 20*l.YOFFSET) 112 self.setSize(w, l.YM + l.YS + l.TEXT_HEIGHT + h) 113 114 # create stacks 115 x0 = l.XM + l.XS * 3 // 2 116 x, y = x0, l.YM 117 for i in range(4): 118 stack = self.Foundation_Class(x, y, self, 119 mod=13, dir=i+1, base_rank=i) 120 s.foundations.append(stack) 121 tx, ty, ta, tf = l.getTextAttr(stack, "s") 122 font = self.app.getFont("canvas_default") 123 stack.texts.misc = MfxCanvasText(self.canvas, tx, ty, 124 anchor=ta, font=font) 125 x = x + l.XS 126 self.texts.help = MfxCanvasText( 127 self.canvas, x + l.XM, y + l.CH // 2, text=help, 128 anchor="w", font=self.app.getFont("canvas_fixed")) 129 x = x0 130 y = l.YM + l.YS + l.TEXT_HEIGHT 131 for i in range(4): 132 s.rows.append(self.RowStack_Class(x, y, self)) 133 x = x + l.XS 134 self.setRegion(s.rows, (-999, y-l.CH//2, 999999, 999999)) 135 x = l.XM 136 s.talon = WasteTalonStack(x, y, self, max_rounds=1) 137 l.createText(s.talon, "n") 138 y = y + l.YS 139 s.waste = WasteStack(x, y, self, max_cards=1) 140 141 # define stack-groups 142 l.defaultStackGroups() 143 144 # 145 # game overrides 146 # 147 148 def _shuffleHook(self, cards): 149 # prepare first cards 150 topcards = [None] * 4 151 for c in cards[:]: 152 if c.rank <= 3 and topcards[c.rank] is None: 153 topcards[c.rank] = c 154 cards.remove(c) 155 topcards.reverse() 156 return cards + topcards 157 158 def startGame(self): 159 self.startDealSample() 160 self.s.talon.dealRow(rows=self.s.foundations) 161 self.s.talon.dealCards() # deal first card to WasteStack 162 163 def getHighlightPilesStacks(self): 164 return () 165 166 167# ************************************************************************ 168# * Hopscotch 169# ************************************************************************ 170 171class Hopscotch(Calculation): 172 def _shuffleHook(self, cards): 173 # prepare first cards 174 topcards = [None] * 4 175 for c in cards[:]: 176 if c.suit == 0 and c.rank <= 3 and topcards[c.rank] is None: 177 topcards[c.rank] = c 178 cards.remove(c) 179 topcards.reverse() 180 return cards + topcards 181 182 183# ************************************************************************ 184# * Betsy Ross 185# ************************************************************************ 186 187class BetsyRoss(Calculation): 188 189 # 190 # game layout 191 # 192 193 def createGame(self): 194 # create layout 195 l, s = Layout(self), self.s 196 help, text_width = self._getHelpText() 197 text_width += 2*l.XM 198 199 # set window 200 self.setSize(5.5*l.XS+l.XM+text_width, l.YM+3*l.YS+l.TEXT_HEIGHT) 201 202 # create stacks 203 x0 = l.XM + l.XS * 3 // 2 204 x, y = x0, l.YM 205 for i in range(4): 206 stack = BetsyRoss_Foundation(x, y, self, base_rank=i, 207 max_cards=1, max_move=0, max_accept=0) 208 s.foundations.append(stack) 209 x += l.XS 210 x = x0 211 y = l.YM + l.YS 212 for i in range(4): 213 stack = BetsyRoss_Foundation(x, y, self, base_rank=2*i+1, 214 mod=13, dir=i+1, 215 max_cards=12, max_move=0) 216 tx, ty, ta, tf = l.getTextAttr(stack, "s") 217 font = self.app.getFont("canvas_default") 218 stack.texts.misc = MfxCanvasText(self.canvas, tx, ty, 219 anchor=ta, font=font) 220 s.foundations.append(stack) 221 x += l.XS 222 self.texts.help = MfxCanvasText(self.canvas, x + l.XM, y + l.CH // 2, 223 text=help, anchor="w", 224 font=self.app.getFont("canvas_fixed")) 225 x = l.XM 226 s.talon = WasteTalonStack(x, y, self, max_rounds=3) 227 l.createText(s.talon, "n") 228 l.createRoundText(s.talon, 'nnn') 229 y += l.YS 230 s.waste = WasteStack(x, y, self) 231 l.createText(s.waste, "s") 232 233 # define stack-groups 234 l.defaultStackGroups() 235 236 # 237 # game overrides 238 # 239 240 def _shuffleHook(self, cards): 241 # prepare first cards 242 topcards = [None] * 8 243 for c in cards[:]: 244 if c.rank <= 3 and topcards[c.rank] is None: 245 topcards[c.rank] = c 246 cards.remove(c) 247 elif c.rank in (1, 3, 5, 7): 248 i = 4 + (c.rank - 1) // 2 249 if topcards[i] is None: 250 topcards[i] = c 251 cards.remove(c) 252 topcards.reverse() 253 return cards + topcards 254 255 256# ************************************************************************ 257# * One234 258# ************************************************************************ 259 260class One234_Foundation(BetsyRoss_Foundation): 261 def canMoveCards(self, cards): 262 if not BetsyRoss_Foundation.canMoveCards(self, cards): 263 return False 264 return len(self.cards) > 1 265 266 def updateText(self): 267 BetsyRoss_Foundation.updateText(self, update_empty=False) 268 269 270class One234_RowStack(BasicRowStack): 271 # clickHandler = BasicRowStack.doubleclickHandler 272 pass 273 274 275class One234(Calculation): 276 Foundation_Class = One234_Foundation 277 RowStack_Class = StackWrapper(One234_RowStack, max_move=1, max_accept=0) 278 279 def createGame(self): 280 # create layout 281 l, s = Layout(self, TEXT_HEIGHT=40), self.s 282 help, text_width = self._getHelpText() 283 text_width += 2*l.XM 284 285 # set window 286 # (piles up to 20 cards are playable in default window size) 287 w = l.XM+max(4*l.XS+text_width, 8*l.XS) 288 h = l.YM+2*l.YS+5*l.YOFFSET+l.TEXT_HEIGHT+l.YS 289 self.setSize(w, h) 290 291 # create stacks 292 x, y = l.XM, l.YM 293 for i in range(4): 294 stack = self.Foundation_Class(x, y, self, 295 mod=13, dir=i+1, base_rank=i) 296 s.foundations.append(stack) 297 tx, ty, ta, tf = l.getTextAttr(stack, "s") 298 font = self.app.getFont("canvas_default") 299 stack.texts.misc = MfxCanvasText(self.canvas, tx, ty, 300 anchor=ta, font=font) 301 x = x + l.XS 302 self.texts.help = MfxCanvasText( 303 self.canvas, x + l.XM, y + l.CH // 2, text=help, 304 anchor="w", font=self.app.getFont("canvas_fixed")) 305 x, y = l.XM, l.YM+l.YS+l.TEXT_HEIGHT 306 for i in range(8): 307 s.rows.append(self.RowStack_Class(x, y, self)) 308 x = x + l.XS 309 310 s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) 311 312 # define stack-groups 313 l.defaultStackGroups() 314 315 def _shuffleHook(self, cards): 316 return cards 317 318 def startGame(self): 319 self._startDealNumRows(4) 320 self.s.talon.dealRow() 321 self.s.talon.dealRow() 322 self.s.talon.dealRow(rows=self.s.foundations) 323 324 325# ************************************************************************ 326# * Senior Wrangler 327# ************************************************************************ 328 329class SeniorWrangler_Talon(DealRowTalonStack): 330 331 def canDealCards(self): 332 if self.round == self.max_rounds: 333 return False 334 return not self.game.isGameWon() 335 336 def dealCards(self, sound=False): 337 num_cards = 0 338 r = self.game.s.rows[self.round-1] 339 if not r.cards: 340 self.game.nextRoundMove(self) 341 return 1 342 if sound: 343 self.game.startDealSample() 344 old_state = self.game.enterState(self.game.S_DEAL) 345 while r.cards: 346 self.game.flipMove(r) 347 self.game.moveMove(1, r, self, frames=4, shadow=0) 348 self.dealRowAvail(rows=self.game.s.rows[self.round-1:], sound=False) 349 while self.cards: 350 num_cards += self.dealRowAvail(sound=False) 351 self.game.nextRoundMove(self) 352 self.game.leaveState(old_state) 353 if sound: 354 self.game.stopSamples() 355 return num_cards 356 357 358class SeniorWrangler_RowStack(BasicRowStack): 359 # clickHandler = BasicRowStack.doubleclickHandler 360 pass 361 362 363class SeniorWrangler(Game): 364 365 def createGame(self): 366 l, s = Layout(self), self.s 367 self.setSize(l.XM+9.5*l.XS, l.YM+3*l.YS) 368 369 x, y = l.XM+1.5*l.XS, l.YM 370 for i in range(8): 371 stack = BetsyRoss_Foundation(x, y, self, base_rank=i, 372 mod=13, dir=i+1, max_move=0) 373 tx, ty, ta, tf = l.getTextAttr(stack, "s") 374 font = self.app.getFont("canvas_default") 375 stack.texts.misc = MfxCanvasText(self.canvas, tx, ty, 376 anchor=ta, font=font) 377 s.foundations.append(stack) 378 x = x + l.XS 379 x, y = l.XM+1.5*l.XS, l.YM+2*l.YS 380 for i in range(8): 381 stack = SeniorWrangler_RowStack(x, y, self, max_accept=0) 382 s.rows.append(stack) 383 stack.CARD_YOFFSET = 0 384 x += l.XS 385 x, y = l.XM, l.YM+l.YS 386 s.talon = SeniorWrangler_Talon(x, y, self, max_rounds=9) 387 l.createRoundText(s.talon, 'nn') 388 389 # define stack-groups 390 l.defaultStackGroups() 391 392 def _shuffleHook(self, cards): 393 top = [] 394 ranks = [] 395 for c in cards[:]: 396 if c.rank in range(8) and c.rank not in ranks: 397 ranks.append(c.rank) 398 cards.remove(c) 399 top.append(c) 400 top.sort(key=lambda x: -x.rank) 401 return cards+top 402 403 def startGame(self): 404 self.s.talon.dealRow(rows=self.s.foundations[:8], frames=0) 405 self._startDealNumRowsAndDealSingleRow(11) 406 407 408# ************************************************************************ 409# * S Patience 410# ************************************************************************ 411 412class SPatience(Game): 413 Hint_Class = Calculation_Hint 414 415 def createGame(self): 416 l, s = Layout(self), self.s 417 self.setSize(l.XM+7.5*l.XS, l.YM+3.8*l.YS) 418 419 x0, y0 = l.XM, l.YM 420 for xx, yy in ((4, 0.4), 421 (3, 0.2), 422 (2, 0.0), 423 (1, 0.2), 424 (0, 0.7), 425 (1, 1.2), 426 (2, 1.4), 427 (3, 1.6), 428 (4, 2.0), 429 (3, 2.6), 430 (2, 2.8), 431 (1, 2.6), 432 (0, 2.4), 433 ): 434 x, y = x0+xx*l.XS, y0+yy*l.YS 435 s.foundations.append(RK_FoundationStack(x, y, self, suit=ANY_SUIT, 436 max_cards=8, mod=13, max_move=0)) 437 438 x, y = l.XM+5.5*l.XS, l.YM+2*l.YS 439 for i in (0, 1): 440 stack = Calculation_RowStack(x, y, self, max_move=1, max_accept=1) 441 stack.CARD_YOFFSET = 0 442 s.rows.append(stack) 443 l.createText(stack, 's') 444 x += l.XS 445 x, y = l.XM+5.5*l.XS, l.YM+l.YS 446 s.talon = WasteTalonStack(x, y, self, max_rounds=1) 447 l.createText(s.talon, 'nw') 448 x += l.XS 449 s.waste = WasteStack(x, y, self, max_cards=1) 450 451 l.defaultStackGroups() 452 453 def _shuffleHook(self, cards): 454 top = [] 455 ranks = [] 456 for c in cards[:]: 457 if c.rank not in ranks: 458 ranks.append(c.rank) 459 cards.remove(c) 460 top.append(c) 461 top.sort(key=lambda x: -x.rank) 462 return cards+top[7:]+top[:7] 463 464 def startGame(self): 465 self.startDealSample() 466 self.s.talon.dealRow(rows=self.s.foundations) 467 self.s.talon.dealCards() 468 469 470# register the game 471registerGame(GameInfo(256, Calculation, "Calculation", 472 GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_SKILL, 473 altnames=("Progression",))) 474registerGame(GameInfo(94, Hopscotch, "Hopscotch", 475 GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_SKILL)) 476registerGame(GameInfo(134, BetsyRoss, "Betsy Ross", 477 GI.GT_1DECK_TYPE, 1, 2, GI.SL_MOSTLY_LUCK, 478 altnames=("Fairest", "Four Kings", "Musical Patience", 479 "Quadruple Alliance", "Plus Belle"))) 480registerGame(GameInfo(550, One234, "One234", 481 GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL)) 482registerGame(GameInfo(653, SeniorWrangler, "Senior Wrangler", 483 GI.GT_2DECK_TYPE, 2, 8, GI.SL_BALANCED)) 484registerGame(GameInfo(704, SPatience, "S Patience", 485 GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED)) 486