4
\$\begingroup\$

Here is my solution for CodeAbbey - Blackjack Counting

My code passes all tests on Code Abbey but I think I have some poor implementation specifically with my handling of the Aces in the score but otherwise I think I overall used good practices and design.

def calculate_player_hand(player_hand_input):
    """Return the score of a player hand"""
    # Variable to hold score for a hand
    total_player_hand_score = 0

    # Dictionary mapping cards to their score value
    card_values = {"2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "T": 10, "J": 10, "K": 10, "Q": 10}

    # Find score of cards except Aces
    for card in player_hand_input:
        if card == "A":
            continue
        else:
            total_player_hand_score += card_values[card]

    # Return player_hand_score not including Ace card(s)
    return total_player_hand_score


# Return the total amount of Aces in a player's hand
def get_aces_count(player_hand_input):
    """Return the count of Aces"""
    return player_hand_input.count("A")


# Read in test cases
test_cases = int(input())

# List to hold all total scores
total_scores = []

# Iterate through test cases
for i in range(test_cases):

    # Read in player hand
    player_hand = input().split()

    # Variable to hold number of Aces in player hand
    player_score_without_aces = calculate_player_hand(player_hand)

    for j in range(get_aces_count(player_hand)):
        if 21 - player_score_without_aces < 11:
            player_score_without_aces += 1
        else:
            player_score_without_aces += 11

    # Rename variable since value of Aces were added
    total_player_score = player_score_without_aces

    # Add total score to total_scores list
    if total_player_score > 21:
        total_scores.append("Bust")
    else:
        total_scores.append(total_player_score)

# Output all total scores
for total_score in total_scores:
    print(total_score, end=" ")

Thank you.

\$\endgroup\$
  • \$\begingroup\$ What's the score of ace + ace + ten? \$\endgroup\$ – Johnbot Feb 28 at 12:24
3
\$\begingroup\$

As was mentioned - always do separation of implementation and test cases.

For a better/optimized functionality consider the following actions:

calculate_player_hand function

  • card_values = {"2": 2, "3": 3, ...} is better defined as top-level constant called CARDS:

    # Dictionary mapping cards to their score value
    CARDS = {"2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "T": 10, "J": 10, "K": 10, "Q": 10}
    
  • instead of going through redundant variable total_player_hand_score and for loop + continue flow - the entire function can be replaced with concise sum + generator expression approach:

    def calculate_player_hand(player_hand_input):
        """Return the score of a player hand except Ace card(s)"""
        # Variable to hold score for a hand
        return sum(CARDS.get(card) for card in player_hand_input if card != 'A')
    

Iterating through test cases

  • the condition if 21 - player_score_without_aces < 11: can be replaced with a shorter equivalent if score_without_aces >= 11: (player_score_without_aces renamed to score_without_aces):

        for j in range(get_aces_count(player_hand)):
            score_without_aces += 1 if score_without_aces >= 11 else 11
    
  • no need to rename the variable total_player_score = score_without_aces as score_without_aces will contain the last resulting value from the upper for loop:

    total_scores.append("Bust" if score_without_aces > 21 else score_without_aces)
    
|improve this answer|||||
\$\endgroup\$
  • 1
    \$\begingroup\$ The optimizations you added are much more desirable. Will try to emulate that in the future. Thanks \$\endgroup\$ – King Cold Feb 28 at 12:51
  • \$\begingroup\$ Adding a comment for the constant CARDS is an indication that your variable name can be improved. Even something simple like CARD_SCORE would make the code more readable. \$\endgroup\$ – rootlocus Feb 28 at 17:47
5
\$\begingroup\$

You do not provide a testable function that performs the task of returning the desired answer for a specific test case. It is the ace handling that you intermangled with your test code. The main structure of your program structure should look somewhat like

def calculate_player_hand(player_hand_input):
    #[...]
    return score

def read_tests():
    #[...]
    return testcases, answers

for test, answer in zip(read_tests()):
    assert answer == calculate_player_hand(test)

Completely separate the functionality you want to provide from the tests. even split into different files.

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ The separation of responsibilities is really elegant to look at. I will try to do this more in the future. \$\endgroup\$ – King Cold Feb 28 at 12:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.