# Title: Project 3 - Group validation # Author: Rory Healy # Date created - 27th May 2019 # Date modified - 30th May 2019 def convert_0jqk(cards): '''Takes a list of cards and converts special numbers (0, J, Q, K) to their respective values (10, 11, 12, 13) respectively.''' # Replaces letters with numbers. for i in range(len(cards)): # Replaces Kings with their numerical value - 13. if cards[i][0] == 'K': suit = cards[i][1] cards[i] = '13' + suit # Replaces Queens with their numerical value - 12. elif cards[i][0] == 'Q': suit = cards[i][1] cards[i] = '12' + suit # Replaces Jacks with their numerical value - 11. elif cards[i][0] == 'J': suit = cards[i][1] cards[i] = '11' + suit # Replaces '0', representing 10, with its numerical value. elif cards[i][0] == '0': suit = cards[i][1] cards[i] = '10' + suit return cards def convert_10111213(cards): '''Takes a list of cards and converts values (such as 10, 11, 12 and 13) to their respective card representation (0, J, Q, K).''' for i in range(len(cards)): # Replaces 13 with its value - K. if cards[i][:-1] == '13': suit = cards[i][-1] cards[i] = 'K' + suit # Replaces 12 with its value - Q. elif cards[i][:-1] == '12': suit = cards[i][-1] cards[i] = 'Q' + suit # Replaces 11 with its value - J. elif cards[i][:-1] == '11': suit = cards[i][-1] cards[i] = 'J' + suit # Replaces 10 with its value - 0. elif cards[i][:-1] == '10': suit = cards[i][-1] cards[i] = '0' + suit return cards def suits_are_alternating(suits): '''Takes a list of cards and returns True if the suits of the cards are alternating, and False otherwise.''' for i in range(1, len(suits)): if suits[i][1] == suits[i - 1][1]: return False return True def order_a_list(cards): '''Orders a list of cards based their numerical value with Aces last.''' ordered_list = [] numbers_of_cards = [] aces = [] for i in range(len(cards)): if cards[i][:-1] == 'A': aces.append('A') else: numbers_of_cards.append(cards[i][:-1]) numbers_of_cards.sort(key=int) numbers_of_cards += aces for number in numbers_of_cards: for card in cards: if number == card[:-1]: if card in ordered_list: continue ordered_list.append(number + card[-1]) return ordered_list def comp10001go_play(discard_history, player_no, hand): '''Takes a list of lists, "discard_history", of the previous cards played by each of the 4 players, an integer "player_no", and a list of cards held by the player "hand".''' # Handles the case where there is only 1 card left. if len(hand) == 1: return hand[0] # Copies the hand to a list that can be changed without affecting the hand. hhand = [] for card in hand: hhand.append(card) # Sorts the hand by numerical order, and also sorts the discard pile for # the current player by numerical order. hhand = convert_0jqk(hhand) hhand = order_a_list(hhand) current_discard = [] for turn in discard_history: current_discard.append(turn[player_no]) current_discard = convert_0jqk(current_discard) current_discard = order_a_list(current_discard) # Extracts the numbers and suits from the discarded pile. discarded_numbers = [] discarded_suits = [] red_cards = 'DH' for card in current_discard: discarded_numbers.append(card[:-1]) discarded_suits.append(card[-1]) discarded_colours = '' for card in discarded_suits: if card in red_cards: discarded_colours += 'R' else: discarded_colours += 'B' # Preferences n-of-a-kind scoring to maximise points by returning the card # that gives the highest scoring n-of-a-kind. potential_return_cards = [] for i in range(len(hhand)): current_card_number = hhand[i][:-1] for number in discarded_numbers: if current_card_number == number and number != 'A': potential_return_cards.append(hand[i]) # A frequency list of the values of the cards in the discard pile is # created, then sorted by the frequency. The card with the highest # frequency is always preferred over the card with the highest value due to # the growth of x!. freq_dict = {} for number in discarded_numbers: if number not in freq_dict.keys(): freq_dict[number] = 1 else: freq_dict[number] += 1 # Generates a list of tuple of items where the values and keys are swapped. dict_items = [] for item in freq_dict.items(): key, value = item dict_items.append((value, key)) # Sorts the tuples by frequency then flips the tuples again. dict_items = sorted(dict_items, reverse=True) sorted_items = [] for item in dict_items: value, key = item sorted_items.append((key, value)) # Selects the card with the highest frequency from the list of potential # cards that could be returned. for item in sorted_items: key, value = item for card in potential_return_cards: if key == card: return card # If there aren't any cards that fit in a run, this returns the lowest # valued card to minimise the points subtracted from singleton cards. hand_numbers = [] for i in range(len(hhand)): if hhand[i][:-1] == 'A': continue hand_numbers.append(int(hhand[i][:-1])) for i in range(len(hand)): if hhand[i][:-1] == 'A': continue elif int(hhand[i][:-1]) == min(hand_numbers): return hand[i] def is_run(cards, aces): '''Takes two lists of cards (represented as strings), cards and aces, and returns a list of the lists of runs that are possible with all the given cards.''' return_list = [] possible_runs = [] original_cards_len = len(cards) for i in range(1, len(cards)): current_card = cards[0] current_run = [] if len(possible_runs) > 0: # This is to prevent any IndexErrors due to the use of a while # loop within a for loop. for run in possible_runs: if len(run) + len(cards) != original_cards_len: break else: continue break n = 1 while n < len(cards): if len(cards) == 1: break # This will check that the cards form a run. elif int(current_card[:-1]) == int(cards[i][:-1]) - n: current_run.append(cards[i]) cards.remove(cards[i]) n += 1 if len(current_run) > 0: current_run.append(current_card) possible_runs.append(current_run) cards.remove(current_card) # Numerically sorts the runs. for i in range(len(possible_runs)): current_run = order_a_list(possible_runs[i]) possible_runs.pop(i) possible_runs.insert(i, current_run) # Inserts any aces where necessary. for run in possible_runs: for i in range(len(run)): if i == len(run) - 1: break elif int(run[i][:-1]) != (int(run[i + 1][:-1]) - 1): if len(aces) > 0: run.insert(i + 1, aces[0]) aces.pop(0) break else: for card in run: cards.append(card) possible_runs.remove(run) break # Checks for alternating suits. for run in possible_runs: if not suits_are_alternating(run): for a_card in run: card = a_card cards.append(a_card) possible_runs.remove(run) # Returns all the runs and leftover cards. return_list.append(possible_runs) return_list.append(cards) return_list.append(aces) return return_list def comp10001go_group(discard_history, player_no): '''Takes a list of the lists of cards played by each player each turn, "discard_history", and an integer "player_no", and returns a list of lists of cards based on the discard history of the player that could be used in scoring.''' # Extracts the cards that the player has discarded, and orders them into a# # list. discarded_cards = [] discarded_numbers = [] for turn in discard_history: discarded_cards.append(turn[player_no]) discarded_cards = convert_0jqk(discarded_cards) discarded_cards = order_a_list(discarded_cards) for card in discarded_cards: number = card[:-1] discarded_numbers.append(number) # Identifies n-of-a-kind cards. freq_dict = {} for number in discarded_numbers: if number not in freq_dict.keys(): freq_dict[number] = 1 else: freq_dict[number] += 1 # Generates a list of tuple of items where the values and keys are swapped. dict_items = [] for item in freq_dict.items(): key, value = item dict_items.append((value, key)) # Sorts the tuples by frequency then flips the tuples again. dict_items = sorted(dict_items, reverse=True) sorted_items = [] for item in dict_items: value, key = item sorted_items.append([key, value]) # Using "sorted_items", we then need to identify cards who's number occurs # more than once and removes them from "discarded_cards" and add them to a # list to be returned. return_list = [] n_of_a_kind_list = [] for cards in sorted_items: if cards[1] > 1: for card in discarded_cards: if card[0] == 'A': continue elif int(card[:-1]) == int(cards[0]): n_of_a_kind_card = card n_of_a_kind_list.append(n_of_a_kind_card) for card in n_of_a_kind_list: for cards in discarded_cards: if card == cards: discarded_cards.remove(cards) if len(n_of_a_kind_list) > 1: return_list.append(n_of_a_kind_list) # Using the cards leftover from the previous check, we need to identify # runs in the list of discarded cards. To do this, the aces are extracted # and stored as a seperate list, then the first list is checked to see if a # run is able to be made and if so, then it is added to the returned list. ace_list = [] for card in discarded_cards: if card[0] == 'A': ace_list.append(card) for card in ace_list: for discard in discarded_cards: if discard == card: discarded_cards.remove(card) runs = is_run(discarded_cards, ace_list) if len(runs[0]) > 0: for run in runs[0]: for card in run: for discard in discarded_cards: if discard == card: discarded_cards.remove(card) return_list.append(run) # Checks for singletons and adds them to a list to be added to the return # list. if len(discarded_cards) > 0: for card in discarded_cards: return_list.append([card]) if len(runs[2]) > 0: for card in runs[2]: return_list.append([card]) for lst in return_list: tmp = convert_10111213(lst) return_list.insert(return_list.index(lst), tmp) return_list.remove(lst) return return_list