1# -*- coding: utf-8 -*- 2# vim:fenc=utf-8 3# 4# Copyright © 2019 Shlomi Fish <shlomif@cpan.org> 5# 6# Distributed under terms of the Expat license. 7 8""" 9 10""" 11 12from pysol_cards.cards import Card, createCards 13from pysol_cards.random import shuffle 14 15from six import print_ 16 17 18def empty_card(): 19 ret = Card(0, 0, 0) 20 ret.empty = True 21 return ret 22 23 24class Columns(object): 25 def __init__(self, num): 26 self.cols = [[] for _ in range(num)] 27 28 def add(self, idx, card): 29 self.cols[idx].append(card) 30 31 def rev(self): 32 self.cols.reverse() 33 34 35class Board(object): 36 def __init__(self, num_columns, with_freecells=False, 37 with_talon=False, with_foundations=False): 38 self.with_freecells = with_freecells 39 self.with_talon = with_talon 40 self.with_foundations = with_foundations 41 self.raw_foundations_cb = None 42 self.raw_foundations_line = None 43 self.columns = Columns(num_columns) 44 if self.with_freecells: 45 self.freecells = [] 46 if self.with_talon: 47 self.talon = [] 48 if self.with_foundations: 49 self.foundations = [empty_card() for s in range(4)] 50 self._lines = [] 51 52 def add_line(self, string): 53 self._lines.append(string) 54 55 def reverse_cols(self): 56 self.columns.rev() 57 58 def add(self, idx, card): 59 self.columns.add(idx, card) 60 61 def add_freecell(self, card): 62 if not self.with_freecells: 63 raise AttributeError("Layout does not have freecells!") 64 self.freecells.append(card) 65 66 def add_talon(self, card): 67 if not self.with_talon: 68 raise AttributeError("Layout does not have a talon!") 69 self.talon.append(card) 70 71 def put_into_founds(self, card): 72 if not self.with_foundations: 73 raise AttributeError("Layout does not have foundations!") 74 res = self.foundations[card.suit].rank + 1 == card.rank 75 if res: 76 self.foundations[card.suit] = card 77 return res 78 79 def print_foundations(self, renderer): 80 cells = [] 81 for f in [2, 0, 3, 1]: 82 if not self.foundations[f].empty: 83 cells.append(renderer.found_s(self.foundations[f])) 84 85 if len(cells): 86 self.add_line("Foundations:" + ("".join([" " + s for s in cells]))) 87 88 def gen_lines(self, renderer): 89 self._lines = [] 90 if self.with_talon: 91 self.add_line("Talon: " + renderer.l_concat(self.talon)) 92 if self.with_foundations: 93 self.print_foundations(renderer) 94 if self.raw_foundations_cb: 95 self.add_line(self.raw_foundations_cb(renderer)) 96 elif self.raw_foundations_line: 97 self.add_line(self.raw_foundations_line) 98 if self.with_freecells: 99 self.add_line("Freecells: " + renderer.l_concat(self.freecells)) 100 101 self._lines += [renderer.l_concat(c) for c in self.columns.cols] 102 103 def calc_string(self, renderer): 104 self.gen_lines(renderer) 105 return "".join(l + "\n" for l in self._lines) 106 107 108class Game(object): 109 REVERSE_MAP = \ 110 { 111 "freecell": 112 ["forecell", "bakers_game", 113 "ko_bakers_game", "kings_only_bakers_game", 114 "relaxed_freecell", "eight_off"], 115 "der_katz": 116 ["der_katzenschwantz", "die_schlange"], 117 "seahaven": 118 ["seahaven_towers", "relaxed_seahaven", 119 "relaxed_seahaven_towers"], 120 "bakers_dozen": [], 121 "gypsy": [], 122 "klondike": 123 ["klondike_by_threes", 124 "casino_klondike", "small_harp", "thumb_and_pouch", 125 "vegas_klondike", "whitehead"], 126 "simple_simon": [], 127 "yukon": [], 128 "beleaguered_castle": 129 ["streets_and_alleys", "citadel"], 130 "fan": [], 131 "black_hole": [], 132 "all_in_a_row": [], 133 "golf": [], 134 } 135 136 GAMES_MAP = {} 137 for k, v in REVERSE_MAP.items(): 138 for name in [k] + v: 139 GAMES_MAP[name] = k 140 141 def __init__(self, game_id, game_num, which_deals, max_rank=13): 142 self.game_id = game_id 143 self.game_num = game_num 144 self.which_deals = which_deals 145 self.max_rank = max_rank 146 self.game_class = self.GAMES_MAP[self.game_id] 147 if not self.game_class: 148 raise ValueError("Unknown game type " + self.game_id + "\n") 149 150 def is_two_decks(self): 151 return self.game_id in ("der_katz", "der_katzenschwantz", 152 "die_schlange", "gypsy") 153 154 def get_num_decks(self): 155 return 2 if self.is_two_decks() else 1 156 157 def calc_deal_string(self, game_num, renderer): 158 self.game_num = game_num 159 self.deal() 160 getattr(self, self.game_class)() 161 return self.board.calc_string(renderer) 162 163 def calc_layout_string(self, renderer): 164 self.deal() 165 getattr(self, self.game_class)() 166 return self.board.calc_string(renderer) 167 168 def print_layout(self, renderer): 169 print_(self.calc_layout_string(renderer), sep='', end='') 170 171 def new_cards(self, cards): 172 self.cards = cards 173 self.card_idx = 0 174 175 def deal(self): 176 cards = shuffle(createCards(self.get_num_decks(), 177 self.max_rank), 178 self.game_num, self.which_deals) 179 cards.reverse() 180 self.new_cards(cards) 181 182 def __iter__(self): 183 return self 184 185 def no_more_cards(self): 186 return self.card_idx >= len(self.cards) 187 188 def __next__(self): 189 if self.no_more_cards(): 190 raise StopIteration 191 c = self.cards[self.card_idx] 192 self.card_idx += 1 193 return c 194 195 def next(self): 196 return self.__next__() 197 198 def add(self, idx, card): 199 self.board.add(idx, card) 200 201 def add_freecell(self, card): 202 self.board.add_freecell(card) 203 204 def cyclical_deal(self, num_cards, num_cols, flipped=False): 205 for i in range(num_cards): 206 self.add(i % num_cols, next(self).flip(flipped=flipped)) 207 208 def add_all_to_talon(self): 209 for c in self: 210 self.board.add_talon(c) 211 212 def add_empty_fc(self): 213 self.add_freecell(empty_card()) 214 215 def _shuffleHookMoveSorter(self, cards, cb, ncards): 216 extracted, i, new = [], len(cards), [] 217 for c in cards: 218 select, ord_ = cb(c) 219 if select: 220 extracted.append((ord_, i, c)) 221 if len(extracted) >= ncards: 222 new += cards[(len(cards) - i + 1):] 223 break 224 else: 225 new.append(c) 226 i -= 1 227 return new, [x[2] for x in reversed(sorted(extracted))] 228 229 def _shuffleHookMoveToBottom(self, inp, cb, ncards=999999): 230 cards, scards = self._shuffleHookMoveSorter(inp, cb, ncards) 231 return scards + cards 232 233 def _shuffleHookMoveToTop(self, inp, cb, ncards=999999): 234 cards, scards = self._shuffleHookMoveSorter(inp, cb, ncards) 235 return cards + scards 236 237 def all_in_a_row(game): 238 game.board = Board(13) 239 game.cards = game._shuffleHookMoveToTop( 240 game.cards, 241 lambda c: (c.id == 13, c.suit), 242 1) 243 game.cyclical_deal(52, 13) 244 game.board.raw_foundations_line = 'Foundations: -' 245 246 def bakers_dozen(game): 247 n = 13 248 cards = list(reversed(game.cards)) 249 for i in [i for i, c in enumerate(cards) if c.is_king()]: 250 j = i % n 251 while j < i: 252 if not cards[j].is_king(): 253 cards[i], cards[j] = cards[j], cards[i] 254 break 255 j += n 256 game.new_cards(cards) 257 game.board = Board(13) 258 game.cyclical_deal(52, 13) 259 260 def beleaguered_castle(game): 261 game.board = Board(8, with_foundations=True) 262 if game.game_id in ('beleaguered_castle', 'citadel'): 263 new = [] 264 for c in game: 265 if c.is_ace(): 266 game.board.put_into_founds(c) 267 else: 268 new.append(c) 269 game.new_cards(new) 270 for _ in range(6): 271 for s in range(8): 272 c = next(game) 273 cond1 = game.game_id == 'citadel' 274 if not (cond1 and game.board.put_into_founds(c)): 275 game.add(s, c) 276 if game.no_more_cards(): 277 break 278 if game.game_id == 'streets_and_alleys': 279 game.cyclical_deal(4, 4) 280 281 def black_hole(game): 282 game.board = Board(17) 283 game.cards = game._shuffleHookMoveToBottom( 284 game.cards, 285 lambda c: (c.id == 13, c.suit), 286 1) 287 next(game) 288 game.cyclical_deal(52 - 1, 17) 289 game.board.raw_foundations_line = 'Foundations: AS' 290 291 def der_katz(game): 292 is_ds = game.game_id == 'die_schlange' 293 if is_ds: 294 print_('Foundations: H-A S-A D-A C-A H-A S-A D-A C-A') 295 game.board = Board(9) 296 i = 0 297 for c in game: 298 if c.is_king(): 299 i += 1 300 if not (is_ds and c.is_ace()): 301 game.add(i, c) 302 303 def fan(game): 304 game.board = Board(18) 305 game.cyclical_deal(52 - 1, 17) 306 game.add(17, next(game)) 307 308 def freecell(game): 309 is_fc = (game.game_id in ("forecell", "eight_off")) 310 game.board = Board(8, with_freecells=is_fc) 311 max_rank = (game.max_rank - 1 if is_fc else game.max_rank) 312 game.cyclical_deal(4 * max_rank, 8) 313 314 if is_fc: 315 for c in game: 316 game.add_freecell(c) 317 if game.game_id == "eight_off": 318 game.add_empty_fc() 319 320 def golf(game): 321 num_cols = 7 322 game.board = Board(num_cols, with_talon=True) 323 game.cyclical_deal(num_cols * 5, num_cols) 324 game.add_all_to_talon() 325 card = game.board.talon.pop(0) 326 game.board.raw_foundations_cb = lambda renderer: 'Foundations: ' + \ 327 renderer.l_concat([card]) 328 329 def gypsy(game): 330 num_cols = 8 331 game.board = Board(num_cols, with_talon=True) 332 game.cyclical_deal(num_cols * 2, num_cols, flipped=True) 333 game.cyclical_deal(num_cols, num_cols) 334 game.add_all_to_talon() 335 336 def klondike(game): 337 num_cols = 7 338 game.board = Board(num_cols, with_talon=True) 339 for r in range(num_cols - 1, 0, -1): 340 game.cyclical_deal(r, r, flipped=True) 341 game.cyclical_deal(num_cols, num_cols) 342 game.add_all_to_talon() 343 if not (game.game_id == 'small_harp'): 344 game.board.reverse_cols() 345 346 def seahaven(game): 347 game.board = Board(10, with_freecells=True) 348 game.cyclical_deal(50, 10) 349 game.add_empty_fc() 350 for c in game: 351 game.add_freecell(c) 352 353 def simple_simon(game): 354 game.board = Board(10) 355 for num_cards in range(9, 2, -1): 356 game.cyclical_deal(num_cards, num_cards) 357 game.cyclical_deal(10, 10) 358 359 def yukon(game): 360 num_cols = 7 361 game.board = Board(num_cols) 362 for i in range(1, num_cols): 363 for j in range(i, num_cols): 364 game.add(j, next(game).flip()) 365 for i in range(4): 366 for j in range(1, num_cols): 367 game.add(j, next(game)) 368 game.cyclical_deal(num_cols, num_cols) 369