Created
December 1, 2022 06:45
-
-
Save trvswgnr/663eaafdb8aa3af4e1514df9b8d192d1 to your computer and use it in GitHub Desktop.
proj10.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from cards import Card, Deck | |
MENU ='''Prompt the user for an option and check that the input has the | |
form requested in the menu, printing an error message, if not. | |
Return: | |
TT s d: Move card from end of Tableau pile s to end of pile d. | |
TF s d: Move card from end of Tableau pile s to Foundation d. | |
WT d: Move card from Waste to Tableau pile d. | |
WF d: Move card from Waste to Foundation pile d. | |
SW : Move card from Stock to Waste. | |
R: Restart the game (after shuffling) | |
H: Display this menu of choices | |
Q: Quit the game | |
''' | |
def initialize(): | |
""" | |
The function has no parameters and returns the starting state of the game with the four data structures initialized as described above. | |
Check the Game rules section to see how to deal cards to each data structure. | |
Remember that stock is of type Class Deck. Also, we need all tableau cards face down except last cards in each column. | |
Cards are initially face up (as defined by the Card class) so turn all tableau cards face down. | |
Then, turn last card in each tableau column face up (Hint: use the flip_card() method from the Card class). | |
You will need to shuffle the stock when you create it. Use lists to create the structures. | |
""" | |
# initialize the stock ([10♣, 4♥, Q♠, Q♣, 7♠, K♦, 10♦, 2♥, 7♥, 8♣, 9♥, 4♠, 6♣, J♠, 4♦, 4♣, A♦, 9♦, 2♦, 7♦, 10♥, 5♥, A♠]) | |
stock = Deck() | |
stock.shuffle() | |
# initialize the waste | |
waste = [] | |
# initialize the foundation ([[], [], [], []]) | |
foundation = [[],[],[],[]] | |
# initialize the tableau | |
tableau = [[],[],[],[],[],[],[]] | |
# deal cards to the tableau and turn all cards face down except last cards in each column ( tableau = [[ Q♥], [ 3♥, 3♦], [ 6♦, J♣, A♣], [ 2♠, 5♠, 3♣, 9♣], [ 8♠, Q♦, J♥, 5♦, 6♠], [ J♦, 9♠, K♥, 8♥, 6♥, A♥], [ 2♣, K♠, 3♠, K♣, 8♦, 10♠, 7♣]] ) | |
# One card is placed in each of the 7 columns, from left to right | |
# A second card is placed in the rightmost 6 columns, from left to right A third card is placed in the rightmost 5 columns, from left to right A fourth card is placed in the rightmost 4 columns, from left to right A fifth card is placed in the rightmost 3 columns, from left to right | |
# A sixth card is placed in the rightmost 2 columns, from left to right A seventh card is placed in the rightmost column | |
for i in range(7): | |
for j in range(i, 7): | |
tableau[j].append(stock.deal()) | |
# turn all tableau cards face down except last cards in each column | |
if j != i: | |
flip_last(tableau[j]) | |
# The top card in the stock is turned over and placed face up in the waste pile | |
waste.append(stock.deal()) | |
return tableau, stock, foundation, waste | |
def display(tableau, stock, foundation, waste): | |
""" display the game setup """ | |
stock_top_card = "empty" | |
found_top_cards = ["empty","empty","empty","empty"] | |
waste_top_card = "empty" | |
if len(waste): | |
waste_top_card = waste[-1] | |
if len(stock): | |
stock_top_card = "XX" #stock[-1] | |
for i in range(4): | |
if len(foundation[i]): | |
found_top_cards[i] = foundation[i][-1] | |
print() | |
print("{:5s} {:5s} \t\t\t\t\t {}".format("stock","waste","foundation")) | |
print("\t\t\t\t ",end = '') | |
for i in range(4): | |
print(" {:5d} ".format(i+1),end = '') | |
print() | |
print("{:5s} {:5s} \t\t\t\t".format(str(stock_top_card), str(waste_top_card)), end = "") | |
for i in found_top_cards: | |
print(" {:5s} ".format(str(i)), end = "") | |
print() | |
print() | |
print() | |
print() | |
print("\t\t\t\t\t{}".format("tableau")) | |
print("\t\t ", end = '') | |
for i in range(7): | |
print(" {:5d} ".format(i+1),end = '') | |
print() | |
# calculate length of longest tableau column | |
max_length = max([len(stack) for stack in tableau]) | |
for i in range(max_length): | |
print("\t\t ",end = '') | |
for tab_list in tableau: | |
# print card if it exists, else print blank | |
try: | |
print(" {:5s} ".format(str(tab_list[i])), end = '') | |
except IndexError: | |
print(" {:5s} ".format(''), end = '') | |
print() | |
print() | |
def stock_to_waste( stock, waste ): | |
""" | |
Has two parameters: the data structure representing the stock, and the data structure representing the waste. | |
Hint: check that there are cards in the stock before trying to move one to the waste. | |
Remember that stock is of type Class Deck. The function will return True if the move was done successfully. Otherwise, it returns False. | |
""" | |
if len(stock): | |
waste.append(stock.deal()) | |
return True | |
else: | |
return False | |
def waste_to_foundation( waste, foundation, f_num ): | |
''' | |
Function has three parameters: | |
1. The data structure representing the waste. | |
2. The data structure representing the foundations | |
3. a foundation number (the correct index in the foundation). | |
The function will return True if the move is valid (False otherwise). | |
If the move is valid, perform it. | |
To be moved into one of the foundations, the card must be the correct suit and rank: | |
it must be the same suit as the other cards in that foundation, | |
and it must have a rank which is exactly one higher than the card that is currently at the top of the foundation (as above, the Two on the Ace, the Three on the Two, and so on). | |
''' | |
# make sure f_num is valid | |
if f_num < 0 or f_num > 3: | |
return False | |
if len(waste): | |
waste_card = waste[-1] | |
f_target = foundation[f_num] | |
if len(f_target): | |
f_card = f_target[-1] | |
suit_matches = waste_card.suit() == f_card.suit() | |
rank_valid = waste_card.rank() == f_card.rank() + 1 | |
if suit_matches and rank_valid: | |
f_target.append(waste.pop()) | |
flip_last(f_target) | |
return True | |
else: | |
return False | |
else: | |
if waste_card.rank() == 1: | |
f_target.append(waste.pop()) | |
flip_last(f_target) | |
return True | |
else: | |
return False | |
else: | |
return False | |
def waste_to_tableau( waste, tableau, t_num ): | |
''' | |
That function has three parameters: | |
1. the data structure representing the waste | |
2. the data structure representing the tableau | |
3. a column number (the correct index in the tableau). | |
The function will return True if the move is valid (False otherwise). If the move is valid, perform it. | |
To be moved into one of the columns in the tableau, the top card in the waste (talon) must be either a King (if the destination in the Tableau is empty), | |
or the opposite color and exactly one rank lower than the card which is the last face-up card in that column. For example, a red Seven may be moved onto a black Eight. | |
Hint: a red card will have suits as either 2 or 3. A black card will have suits either 1 or 4. | |
''' | |
if len(waste): | |
waste_card = waste[-1] | |
t_target = tableau[t_num] | |
if len(t_target): | |
t_card = t_target[-1] | |
waste_card_black = waste_card.suit() == 1 or waste_card.suit == 4 | |
t_card_black = t_card.suit() == 1 or t_card.suit == 4 | |
rank_valid = waste_card.rank() == t_card.rank() - 1 | |
if waste_card_black != t_card_black and rank_valid: | |
t_target.append(waste.pop()) | |
flip_last(t_target) | |
return True | |
else: | |
return False | |
else: | |
if waste_card.rank() == 13: # if the target is empty and the waste card is a king | |
t_target.append(waste.pop()) | |
flip_last(t_target) | |
return True | |
else: | |
return False | |
return False | |
def tableau_to_foundation( tableau, foundation, t_num, f_num ): | |
# make sure t_num and f_num are valid | |
if t_num < 0 or t_num > 6 or f_num < 0 or f_num > 3: | |
return False | |
t_source = tableau[t_num] | |
f_target = foundation[f_num] | |
if len(t_source): | |
t_card = t_source[-1] | |
if len(f_target): | |
f_card = f_target[-1] | |
rank_valid = t_card.rank() == f_card.rank() + 1 | |
if rank_valid and t_card.suit() == f_card.suit(): | |
f_target.append(t_source.pop()) | |
flip_last(t_source) | |
flip_last(f_target) | |
return True | |
else: | |
return False | |
else: # the foundation column is empty | |
if t_card.rank() == 1: # the tableau card is an ace | |
f_target.append(t_source.pop()) | |
flip_last(t_source) | |
flip_last(f_target) | |
return True | |
else: | |
return False | |
else: | |
return False | |
def flip_last(column): | |
''' | |
Flips the last card in the column face up. | |
''' | |
if len(column) > 0: | |
if not column[-1].is_face_up(): | |
column[-1].flip_card() | |
def tableau_to_tableau( tableau, t_num1, t_num2 ): | |
''' | |
Moves a card from one tableau column to another. | |
Returns True if the move was successful, False otherwise. | |
''' | |
# make sure t_num1 and t_num2 are valid | |
if t_num1 < 0 or t_num1 > 6 or t_num2 < 0 or t_num2 > 6: | |
return False | |
src_col = tableau[t_num1] # source tableau column | |
if not len(src_col): # source column is empty | |
return False | |
src_card = src_col[-1] # last card in source tableau column | |
dest_col = tableau[t_num2] # destination tableau column | |
if not len(dest_col): # destination column is empty | |
src_is_king = src_card.rank() == 13 # true if source card is a king (rank 13) | |
if src_is_king: # source card is a king | |
dest_col.append(src_col.pop()) # remove card from source column and add to destination column | |
flip_last(src_col) | |
flip_last(dest_col) | |
return True | |
return False | |
dest_card = dest_col[-1] # last card in destination tableau column | |
src_card_color = src_card.suit() == 1 or src_card.suit() == 4 # true for black, false for red | |
dest_card_color = dest_card.suit() == 1 or dest_card.suit() == 4 # true for black, false for red | |
src_rank_is_valid = src_card.rank() == dest_card.rank() - 1 # true if source card is one rank lower than destination card | |
src_color_is_valid = src_card_color != dest_card_color # true if source card is opposite color of destination card | |
is_valid_move = src_color_is_valid and src_rank_is_valid # true if source card is one rank lower and opposite color of destination card | |
if is_valid_move: # a valid move was made | |
dest_col.append(src_col.pop()) # move card from source to destination column | |
flip_last(src_col) | |
flip_last(dest_col) | |
return True | |
return False | |
def check_win (stock, waste, foundation, tableau): | |
''' | |
Returns True if the game is in a winning state: all cards are in the foundation, | |
stock is empty, waste is empty and tableau is empty. Otherwise, return False. | |
''' | |
foundation_is_full = False not in [len(col) == 13 for col in foundation] # true if all columns in foundation are full | |
stock_is_empty = not len(stock) # true if stock is empty | |
waste_is_empty = not len(waste) # true if waste is empty | |
tableau_is_empty = not any(tableau) # true if all columns in tableau are empty | |
if foundation_is_full and stock_is_empty and waste_is_empty and tableau_is_empty: | |
return True | |
return False | |
def parse_option( in_str ): | |
'''Prompt the user for an option and check that the input has the | |
form requested in the menu, printing an error message, if not. | |
Return: | |
TT s d: Move card from end of Tableau pile s to end of pile d. | |
TF s d: Move card from end of Tableau pile s to Foundation d. | |
WT d: Move card from Waste to Tableau pile d. | |
WF d: Move card from Waste to Foundation pile d. | |
SW : Move card from Stock to Waste. | |
R: Restart the game (after shuffling) | |
H: Display this menu of choices | |
Q: Quit the game | |
The function has one parameter: the raw input string. It returns a list which has the option specified followed by any arguments entered or returns None, if there is an error. The option returned must be upper case. All error checking of input is done in this function. Error statements are printed from this function (see specifications below). Note that if a list is returned, the list will be in the correct format with correct arguments—making your main function easier. | |
An example of a return (there are multiple variations): | |
return [option_str, source, destination, count] | |
where option_str is uppercase and the three arguments are correct integers (the column number and not the index in the list) Remember that column numbers start from 1 and not from 0. | |
Note that the function test we provide can check that None was returned for an error, but cannot check that a correct error message was printed—correctly printed error messages are only checked when the whole program is tested. | |
If an argument is an integer but is out of range, orprint("Error in Source") | |
print("Error in Destination") | |
as appropriate. | |
If the mTT count is an integer but not positive, | |
print("Error in Count") | |
If any argument is not all digits (e.g. -1) and all other errors. | |
print("Error in option:", in_str) # in_str is the raw input string | |
(The “not all digits” is an attempt to make some error checking easier for you.) Remember that all errors return None after printing the error message. | |
''' | |
option_list = in_str.strip().split() | |
opt_char = option_list[0][0].upper() | |
if opt_char in 'RHQ' and len(option_list) == 1: # correct format | |
return [opt_char] | |
if opt_char == 'S' and len(option_list) == 1: | |
if option_list[0].upper() == 'SW': | |
return ['SW'] | |
if opt_char == 'W' and len(option_list) == 2: | |
if option_list[0].upper() == 'WT' or option_list[0].upper() == 'WF': | |
dest = option_list[1] | |
if dest.isdigit(): | |
dest = int(dest) | |
if option_list[0].upper() == 'WT' and (dest < 1 or dest > 7): | |
print("\nError in Destination") | |
return None | |
if option_list[0].upper() == 'WF' and (dest < 1 or dest > 4): | |
print("\nError in Destination") | |
return None | |
opt_str = option_list[0].strip().upper() | |
return [opt_str,dest] | |
valid_digits = all([option.isdigit() for option in option_list[1:]]) | |
if opt_char == 'T' and len(option_list) == 3 and valid_digits: | |
opt_str = option_list[0].strip().upper() | |
if opt_str in ['TT','TF']: | |
source = int(option_list[1]) | |
dest = int(option_list[2]) | |
# check for valid source values | |
if opt_str in ['TT','TF'] and (source < 1 or source > 7): | |
print("\nError in Source.") | |
return None | |
#elif opt_str == 'MFT' and (source < 0 or source > 3): | |
#print("Error in Source.") | |
#return None | |
# source values are valid | |
# check for valid destination values | |
if (opt_str =='TT' and (dest < 1 or dest > 7)) \ | |
or (opt_str == 'TF' and (dest < 1 or dest > 4)): | |
print("\nError in Destination") | |
return None | |
return [opt_str,source,dest] | |
print("\nError in option:", in_str) | |
return None # none of the above | |
def main(): | |
# initialize the board | |
tableau, stock, foundation, waste = initialize() | |
# display the menu | |
print(MENU) | |
# display the starting board | |
display(tableau, stock, foundation, waste) | |
prompt_msg = "\nInput an option (TT,TF,WT,WF,SW,R,H,Q): " | |
error_msg = "\nInvalid move!\n" | |
won_msg = "\nYou won!\n" | |
input_str = input(prompt_msg) | |
while input_str.upper() != 'Q': | |
options = parse_option(input_str) | |
if options == None: | |
display(tableau, stock, foundation, waste) | |
input_str = input(prompt_msg) | |
continue | |
if options[0] == 'R': | |
tableau, stock, foundation, waste = initialize() | |
display(tableau, stock, foundation, waste) | |
input_str = input(prompt_msg) | |
continue | |
if options[0] == 'H': | |
print(MENU) | |
input_str = input(prompt_msg) | |
continue | |
if options[0] == 'SW': | |
if stock_to_waste(stock, waste): | |
display(tableau, stock, foundation, waste) | |
else: | |
print(error_msg) | |
display(tableau, stock, foundation, waste) | |
input_str = input(prompt_msg) | |
continue | |
if options[0] == 'WT': | |
if waste_to_tableau(waste, tableau, options[1]): | |
display(tableau, stock, foundation, waste) | |
else: | |
print(error_msg) | |
display(tableau, stock, foundation, waste) | |
input_str = input(prompt_msg) | |
continue | |
if options[0] == 'WF': | |
if waste_to_foundation(waste, foundation, options[1]): | |
if check_win(stock, waste, foundation, tableau): | |
print(won_msg) | |
display(tableau, stock, foundation, waste) | |
break | |
else: | |
display(tableau, stock, foundation, waste) | |
else: | |
print(error_msg) | |
display(tableau, stock, foundation, waste) | |
input_str = input(prompt_msg) | |
continue | |
if options[0] == 'TT': | |
if tableau_to_tableau(tableau, options[1], options[2]): | |
display(tableau, stock, foundation, waste) | |
else: | |
print(error_msg) | |
display(tableau, stock, foundation, waste) | |
input_str = input(prompt_msg) | |
continue | |
if options[0] == 'TF': | |
if tableau_to_foundation(tableau, foundation, options[1], options[2]): | |
if check_win(stock, waste, foundation, tableau): | |
print(won_msg) | |
display(tableau, stock, foundation, waste) | |
break | |
else: | |
display(tableau, stock, foundation, waste) | |
else: | |
print(error_msg) | |
display(tableau, stock, foundation, waste) | |
input_str = input(prompt_msg) | |
continue | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment