# make_pysol_freecell_board.py - Program to generate the boards of # PySol for input into Freecell Solver. # # Usage: make_pysol_freecell_board.py [board number] | fc-solve # # Or on non-UNIXes: # # python make_pysol_freecell_board.py [board number] | fc-solve # # This program is platform independent and will generate the same results # on all architectures and operating systems. # # Based on the code by Markus Franz Xaver Johannes Oberhumer. # Modified by Shlomi Fish, 2000 # # Since much of the code here is ripped from the actual PySol code, this # program is distributed under the GNU General Public License. # # # # vim:ts=4:et:nowrap # # ---------------------------------------------------------------------------## # # PySol -- a Python Solitaire game # # Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer # Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer # Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; see the file COPYING. # If not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Markus F.X.J. Oberhumer # # http://wildsau.idv.uni-linz.ac.at/mfx/pysol.html # # ---------------------------------------------------------------------------## # imports import unittest # So the localpaths will be overrided. from pysollib.pysolrandom import constructRandom, LCRandom31, \ random__str2long, random__long2str # PySol imports # /*********************************************************************** # // Abstract PySol Random number generator. # // # // We use a seed of type long in the range [0, MAX_SEED]. # ************************************************************************/ class Card: ACE = 1 KING = 13 def __init__(self, id, rank, suit, print_ts): self.id = id self.rank = rank self.suit = suit self.flipped = False self.print_ts = print_ts self.empty = False def is_king(self): return self.rank == self.KING def is_ace(self): return self.rank == self.ACE def rank_s(self): s = "0A23456789TJQK"[self.rank] if (not self.print_ts) and s == "T": s = "10" return s def suit_s(self): return "CSHD"[self.suit] def to_s(self): if self.empty: return "-" ret = "" ret = ret + self.rank_s() ret = ret + self.suit_s() if self.flipped: ret = "<" + ret + ">" return ret def found_s(self): return self.suit_s() + "-" + self.rank_s() def flip(self, flipped=True): new_card = Card(self.id, self.rank, self.suit, self.print_ts) new_card.flipped = flipped return new_card def is_empty(self): return self.empty class Columns: def __init__(self, num): self.num = num cols = [] for i in range(num): cols.append([]) self.cols = cols def add(self, idx, card): self.cols[idx].append(card) def rev(self): self.cols.reverse() def output(self): s = '' for column in self.cols: s += column_to_string(column) + "\n" return s class Board: def __init__(self, num_columns, with_freecells=False, with_talon=False, with_foundations=False): self.with_freecells = with_freecells self.with_talon = with_talon self.with_foundations = with_foundations self.columns = Columns(num_columns) if (self.with_freecells): self.freecells = [] if (self.with_talon): self.talon = [] if (self.with_foundations): self.foundations = map(lambda s: empty_card(), range(4)) def reverse_cols(self): return self.columns.rev() def add(self, idx, card): return self.columns.add(idx, card) def print_freecells(self): return "FC: " + column_to_string(self.freecells) def print_talon(self): return "Talon: " + column_to_string(self.talon) def print_foundations(self): cells = [] for f in [2, 0, 3, 1]: if not self.foundations[f].is_empty(): cells.append(self.foundations[f].found_s()) if len(cells): return "Foundations:" + ("".join(map(lambda s: " "+s, cells))) def output(self): s = '' if (self.with_talon): s += self.print_talon() + "\n" if (self.with_foundations): s += self.print_foundations() + "\n" if (self.with_freecells): s += self.print_freecells() + "\n" s += self.columns.output() return s def add_freecell(self, card): if not self.with_freecells: raise AttributeError("Layout does not have freecells!") self.freecells.append(card) def add_talon(self, card): if not self.with_talon: raise AttributeError("Layout does not have a talon!") self.talon.append(card) def put_into_founds(self, card): if not self.with_foundations: raise AttributeError("Layout does not have foundations!") if ((self.foundations[card.suit].rank+1) == card.rank): self.foundations[card.suit] = card return True else: return False self.talon.append(card) def empty_card(): ret = Card(0, 0, 0, 1) ret.empty = True return ret def createCards(num_decks, print_ts): cards = [] for deck in range(num_decks): id = 0 for suit in range(4): for rank in range(13): cards.append(Card(id, rank+1, suit, print_ts)) id = id + 1 return cards def column_to_list_of_strings(col): return map(lambda c: c.to_s(), col) def column_to_string(col): return " ".join(column_to_list_of_strings(col)) def flip_card(card_str, flip): if flip: return "<" + card_str + ">" else: return card_str def shuffle(orig_cards, rand): shuffled_cards = list(orig_cards) if isinstance(rand, LCRandom31) and len(shuffled_cards) == 52: # FreeCell mode fcards = [] for i in range(13): for j in (0, 39, 26, 13): fcards.append(shuffled_cards[i + j]) shuffled_cards = fcards # rand.shuffle works in place rand.shuffle(shuffled_cards) return shuffled_cards class Game: REVERSE_MAP = \ { "freecell": ["freecell", "forecell", "bakers_game", "ko_bakers_game", "kings_only_bakers_game", "relaxed_freecell", "eight_off"], "der_katz": ["der_katz", "der_katzenschwantz", "die_schlange"], "seahaven": ["seahaven_towers", "seahaven", "relaxed_seahaven", "relaxed_seahaven_towers"], "bakers_dozen": None, "gypsy": None, "klondike": ["klondike", "klondike_by_threes", "casino_klondike", "small_harp", "thumb_and_pouch", "vegas_klondike", "whitehead"], "simple_simon": None, "yukon": None, "beleaguered_castle": ["beleaguered_castle", "streets_and_alleys", "citadel"], "fan": None, "black_hole": None, "all_in_a_row": None, } def __init__(self, game_id, rand, print_ts): mymap = {} for k in self.REVERSE_MAP.keys(): if self.REVERSE_MAP[k] is None: mymap[k] = k else: for alias in self.REVERSE_MAP[k]: mymap[alias] = k self.games_map = mymap self.game_id = game_id self.print_ts = print_ts self.rand = rand def print_layout(self): game_class = self.lookup() if not game_class: raise ValueError("Unknown game type " + self.game_id + "\n") self.deal() getattr(self, game_class)() return self.board.output() def lookup(self): return self.games_map[self.game_id] def is_two_decks(self): return self.game_id in ("der_katz", "der_katzenschwantz", "die_schlange", "gypsy") def get_num_decks(self): if self.is_two_decks(): return 2 else: return 1 def deal(self): orig_cards = createCards(self.get_num_decks(), self.print_ts) orig_cards = shuffle(orig_cards, self.rand) cards = orig_cards cards.reverse() self.cards = cards self.card_idx = 0 return True def __iter__(self): return self def no_more_cards(self): return self.card_idx >= len(self.cards) def next(self): if self.no_more_cards(): raise StopIteration c = self.cards[self.card_idx] self.card_idx = self.card_idx + 1 return c def new_cards(self, cards): self.cards = cards self.card_idx = 0 def add(self, idx, card): return self.board.add(idx, card) def add_freecell(self, card): return self.board.add_freecell(card) def cyclical_deal(game, num_cards, num_cols, flipped=False): for i in range(num_cards): game.add(i % num_cols, game.next().flip(flipped=flipped)) return i def add_all_to_talon(game): for card in game: game.board.add_talon(card) # These are the games variants: # Each one is a callback. def der_katz(game): if (game.game_id == "die_schlange"): return "Foundations: H-A S-A D-A C-A H-A S-A D-A C-A" game.board = Board(9) col_idx = 0 for card in game: if card.is_king(): col_idx = col_idx + 1 if not ((game.game_id == "die_schlange") and (card.rank == 1)): game.add(col_idx, card) def freecell(game): is_fc = (game.game_id in ('forecell', 'eight_off')) game.board = Board(8, with_freecells=is_fc) if is_fc: game.cyclical_deal(48, 8) for card in game: game.add_freecell(card) if game.game_id == "eight_off": game.add_freecell(empty_card()) else: game.cyclical_deal(52, 8) def seahaven(game): game.board = Board(10, with_freecells=True) game.add_freecell(empty_card()) game.cyclical_deal(50, 10) for card in game: game.add_freecell(card) def bakers_dozen(game): i, n = 0, 13 kings = [] cards = game.cards cards.reverse() for c in cards: if c.is_king(): kings.append(i) i = i + 1 for i in kings: j = i % n while j < i: if not cards[j].is_king(): cards[i], cards[j] = cards[j], cards[i] break j = j + n game.new_cards(cards) game.board = Board(13) game.cyclical_deal(52, 13) def gypsy(game): num_cols = 8 game.board = Board(num_cols, with_talon=True) game.cyclical_deal(num_cols*2, num_cols, flipped=True) game.cyclical_deal(num_cols, num_cols, flipped=False) game.add_all_to_talon() def klondike(game): num_cols = 7 game.board = Board(num_cols, with_talon=True) for r in range(1, num_cols): for s in range(num_cols-r): game.add(s, game.next().flip()) game.cyclical_deal(num_cols, num_cols) game.add_all_to_talon() if not (game.game_id == "small_harp"): game.board.reverse_cols() def simple_simon(game): game.board = Board(10) num_cards = 9 while num_cards >= 3: for s in range(num_cards): game.add(s, game.next()) num_cards = num_cards - 1 for s in range(10): game.add(s, game.next()) def fan(game): game.board = Board(18) game.cyclical_deal(52-1, 17) game.add(17, game.next()) def _shuffleHookMoveSorter(self, cards, func, ncards): # note that we reverse the cards, so that smaller sort_orders # will be nearer to the top of the Talon sitems, i = [], len(cards) for c in cards[:]: select, sort_order = func(c) if select: cards.remove(c) sitems.append((sort_order, i, c)) if len(sitems) >= ncards: break i = i - 1 sitems.sort() sitems.reverse() scards = map(lambda item: item[2], sitems) return cards, scards def _shuffleHookMoveToBottom(self, cards, func, ncards=999999): # move cards to bottom of the Talon (i.e. last cards to be dealt) cards, scards = self._shuffleHookMoveSorter(cards, func, ncards) ret = scards + cards return ret def _shuffleHookMoveToTop(self, cards, func, ncards=999999): # move cards to top of the Talon (i.e. last cards to be dealt) cards, scards = self._shuffleHookMoveSorter(cards, func, ncards) return cards + scards def black_hole(game): game.board = Board(17) # move Ace to bottom of the Talon (i.e. last cards to be dealt) game.cards = game._shuffleHookMoveToBottom( game.cards, lambda c: (c.id == 13, c.suit), 1) game.next() game.cyclical_deal(52-1, 17) return "Foundations: AS" def all_in_a_row(game): game.board = Board(13) # move Ace to bottom of the Talon (i.e. last cards to be dealt) game.cards = game._shuffleHookMoveToTop( game.cards, lambda c: (c.id == 13, c.suit), 1) game.cyclical_deal(52, 13) return "Foundations: -" def beleaguered_castle(game): aces_up = game.game_id in ("beleaguered_castle", "citadel") game.board = Board(8, with_foundations=True) if aces_up: new_cards = [] for c in game: if c.is_ace(): game.board.put_into_founds(c) else: new_cards.append(c) game.new_cards(new_cards) for i in range(6): for s in range(8): c = game.next() if (game.game_id == "citadel") and \ game.board.put_into_founds(c): # Already dealt with this card True else: game.add(s, c) if game.no_more_cards(): break if (game.game_id == "streets_and_alleys"): game.cyclical_deal(4, 4) def yukon(game): num_cols = 7 game.board = Board(num_cols) for i in range(1, num_cols): for j in range(i, num_cols): game.add(j, game.next().flip()) for i in range(4): for j in range(1, num_cols): game.add(j, game.next()) game.cyclical_deal(num_cols, num_cols) class MyTests(unittest.TestCase): def _cmp_board(self, got_s, expected_s, blurb): if not self.assertEqual(got_s, expected_s, blurb): return False return True def test_main(self): rand = constructRandom('24') def test_24(blurb): game = Game("freecell", rand, True) got_s = game.print_layout() self.assertEqual(got_s, '''4C 2C 9C 8C QS 4S 2H 5H QH 3C AC 3H 4H QD QC 9S 6H 9H 3S KS 3D 5D 2S JC 5C JH 6D AS 2D KD TH TC TD 8D 7H JS KH TS KC 7C AH 5S 6S AD 8H JD 7S 6C 7D 4D 8S 9D ''', blurb) # TEST test_24('Deal 24') rand.reset() # TEST test_24('MS deal after reset.') rand = constructRandom('ms123456') game = Game("freecell", rand, True) # TEST got_s = game.print_layout() self.assertEqual(got_s, '''QD TC AS KC AH KH 6H 6D TD 8D TH 7C 2H 9C AC AD 5C 5H 8C 9H 9D JS 8S 4D 4C 2S 7D 3C 7H 7S 9S 2C JC 5S 5D 3S 3D 3H KD JH 6C QS 4S 2D KS TS JD QH 6S 4H QC 8H ''', 'Microsoft Deal 123456') rand = constructRandom('123456') game = Game("freecell", rand, True) # TEST self._cmp_board(game.print_layout(), '''3D 6C AS TS QC 8D 4D 2D TC 4H JD TD 2H 5C 2C 8S AH KD KH 5S 7C 9C 8C QH 3C 5D 9S QD AC 9D 7H 6D KS JH 6H TH 8H QS 7D JC 4C 2S 3S 6S 5H 3H KC JS 9H 4S 7S AD ''', 'PySolFC deal No. 123456') rand = constructRandom('ms3000000000') game = Game("freecell", rand, True) # TEST self._cmp_board(game.print_layout(), '''8D TS JS TD JH JD JC 4D QS TH AD 4S TC 3C 9H KH QH 4C 5C KD AS 9D 5D 8S 4H KS 6S 9S 6H 2S 7H 3D KC 2C 9C 7C QC 7S QD 7D 6C 3H 8H AC 6D 3S 8C AH 2H 5H 2D 5S ''', 'Microsoft Deal #3E9 - long seed.') rand = constructRandom('ms6000000000') game = Game("freecell", rand, True) # TEST got_s = game.print_layout() self.assertEqual(got_s, '''2D 2C QS 8D KD 8C 4C 3D AH 2H 4H TS 6H QD 4D JS AD 6S JH JC JD KH 3H KS AS TC 5D AC TD 7C 9C 7H 3C 3S QH 9H 9D 5S 7S 6C 5C 5H 2S KC 9S 4S 6D QC 8S TH 7D 8H ''', 'Microsoft Deal #6E9 - extra long seed.') inp = 'ms12345678' got = random__long2str(random__str2long(inp)) # TEST self.assertEqual(got, inp, 'long2str ms roundtrip.') inp = '246007891097' got = random__long2str(random__str2long(inp)) # TEST self.assertEqual(got, inp, 'long2str PySolFC roundtrip.') proto_inp = '246007891097' inp = random__str2long(proto_inp) got = random__str2long(random__long2str(inp)) # TEST self.assertEqual(got, inp, 'str2long PySolFC roundtrip.')