351 lines
12 KiB
Python
351 lines
12 KiB
Python
|
# 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
|