8
\$\begingroup\$

It is a simple Poker balance/bid tracker to automate calculations for players' scores.

It is a project I made to practice the concepts of lists and indices. And thus heavily relies on indexing and list search.

I know I could have used dictionaries, but I chose not to get familiar with previously mentioned concepts.

I'm still learning using MIT online course, so I'm only familiar with a limited number of topics, including Branching and Iteration, String Manipulation / bisectional search / Guess And Check / Approximation, Decomposition / Abstraction / Function Definition, Sequences including lists, tuples, and Dictionaries.

Please consider that when providing an answer.

Kindly see if this code can be improved in any way without using dictionaries.

import string

def new_line(n):
    '''
    prints (n) new lines
    '''
    for n in range(n):
        print()

def x_player(list_secondary, string, list_primary):
    '''
    returns the item from a list(list_primary) at 
    the index of another string (string) in another list(list_secondary)
    '''

    return list_primary[list_secondary.index(string)]


def get_space(pos_list, pos_string, string, extra_length):
    '''
    returns the given string(string) argument multiplied by 
    the length of the longest string in a list(pos_list)
    minus the length of another_string (pos_string)
    plus any extra length determined by the user.
    '''

    lengths = [ len(string) for string in pos_list]
    h_length = max(lengths) + extra_length
    return ( string * ( h_length - len(pos_string)))

def list_players(player_names, player, list_):
    ''' 
    prints the player name + appropriate space + an item 
    at the index of : (player in player_names) from a different
    list (list_)
    '''
    print ( player + get_space(player_names, player, ' ', 1) + ': '+ str(x_player(player_names, player, list_)))

def list_balances(player_names, balances):
    '''
    lists the player names and their balances.
    '''
    print('Current Balances:')
    for player in player_names:
        list_players(player_names, player, balances)

def list_bids(player_names, bids):
    '''
    lists the player names and their bids
    '''
    print('Current Bids:')
    for player in player_names:
        list_players(player_names, player, bids)

def poker():
    '''
    starts the poker score counter
    '''
#   ask for the number of players and their names.    
    n_players = int(input('Number Of Players?... '))
    player_names = []
    for n in range(n_players):
        player = input('Player # ' + str(n+1) + ' .... ')
        player_names.append(player)
# iniate lists for balances, bids and list to determine if a player has folded or not.
    balances = [ 90 for n in range(n_players)]  
    bids = [10 for n in range(len(player_names))]
    fold_code = [ 0 for n in range(len(player_names)) ]
    #balance_codes = [ 0 for n in range(n_players)]
    # this variable will determine if a round is over or not.
    this_round = ''
#   total bid:
    total = sum(bids)
#   getting user's confirmation to start the counter.
#   this variable will not be used again.
    stop = input('Press Enter To Continue...')
    print('Starting Game...')
#   Intitial Balance / Bid listing.
    list_balances(player_names, balances)
    list_bids(player_names, bids)
    while True:
#       to determine if a round is over.       
        if this_round == 'y':
            print('Who Won?')
            winner = input()
#           adding the total bid to the winner's balance.
            balances[player_names.index(winner)] += total
            new_line(1)
            print(winner, 'Won This Round')
            new_line(1)
            list_balances(player_names, balances)
#           checking for players who are out.
#           a for loop that iterates n_player times. to avoid
#           indices being out of range after removing a player from the list.       
            for var in range(n_players):
                if 0 in balances:
                    print (player_names[balances.index(0)].upper(), 'Is Out...')
                    player_names.pop(balances.index(0))
                    bids.pop(balances.index(0))
                    balances.pop(balances.index(0))
#           self-explanatory using the print statement.
            for a player in player_names:
                balances[player_names.index(player)] -= 10
#           re-initializing bids and fold code after each round.
#           fold code is used to determine if a player has folded for the round.
            bids = [ 10 for n in range(len(player_names))]
            fold_code = [0 for n in range(len(player_names))]
            print('Deducting 10$ from all players for the new round...')
            new_line(1)
            list_balances(player_names, balances)
            new_line(1)
            list_bids(player_names, bids)
            new_line(1)
            print('-' * 35)
        for player in player_names:
#           if a player folds for the round the value in the list ( fold-code) 'at the same index'
#           gets assigned to be '1' therefore the loop ends and moves on to the next player.
            if fold_code[player_names.index(player)] == 1:
                continue
#           asking for user input ( player action for the round.
            print('>' * 15, player.upper(), '>' * 15 )
            action = input ()
#           c i.e : 'call', which then evaluates the difference between the max bid and 
#           the current player's bid and later on in the program subtracts it from the \
#           player's balance and adds it to the total bid.
            if action == 'c':
                bid = max(bids) - x_player(player_names, player, bids)
                print(player, 'Called...')
#           # r i.e : 'raise', does the same process using the 'bid' variable but adds the raise value to it.
            elif action == 'r':
                print('Raise By?')
                r = int(input())
                bid = max(bids) - x_player(player_names, player, bids) + r
                print(player, 'Raised By', r)
#           fold makes the 'bid' variable = 0 and makes the program skip the player's turn until the round is over.
            elif action == 'f' :
               fold_code[player_names.index(player)] = 1
               bid = 0
               print(player, 'Folded For the round')
#           similar to fold but doesn't skip the player's turn           
            else:
                bid = 0
                print(player, 'Checked...')
#           deducting the 'bid' variable from the player's balance and adding it to the respective value in the bids list.
            balances[player_names.index(player)] -= bid
            bids[player_names.index(player)] += bid
            total = sum(bids)
            new_line(1)
            list_balances(player_names, balances)
            new_line(1)
            list_bids(player_names, bids)
            new_line(1)
            print('Total Bid : ' + str(total))
            new_line(2)
            print('-' * 35)
#       the decision to end a round is left to the user, plans to implement automatic round termination has been made.       
        print('Round Over?')
        this_round = input()
new_line(20)
poker()




```
\$\endgroup\$
2
\$\begingroup\$

Docstrings

You should always use triple double quotes for docstrings. ''' should be replaced with """

Comments

Don't fill your code with comments. The actual code is harder to read. Comments should be used only while explaining something which others can't understand by themselves.

Making the code shorter

Functions other than poker

  • You can remove import string as you don't use it

    1. You can use print() instead of creating a new function
    2. You can use list_players directly, instead of list_bids or list_balances

In the poker function

  • n_players = int(input('Number Of Players?... '))
    player_names = []
    
    for n in range(n_players):
        player = input('Player # ' + str(n+1) + ' .... ')
        player_names.append(player)
    

    can be rewritten as

    n_players = int(input('Number Of Players?... '))
    player_names = [input(f'Player # {n+1} .... ') for n in range(n_players)]
    
  • While beginning, len(player_names) is the same as n_players. Therefore, we can write

    balances = [ 90 for n in range(n_players)]  
    bids = [10 for n in range(len(player_names))]
    fold_code = [ 0 for n in range(len(player_names)) ]
    

    as

    balances = [90] * n_players
    bids = [10] * n_players
    fold_code = [0] * n_players
    

Final code after implementing the above ideas and making some negligible changes:

def get_space(pos_list, pos_string, string, extra_length):
    h_length = max(len(string) for string in pos_list) + extra_length
    return string * ( h_length - len(pos_string))

def list_players(player_names, n_players, list_):
    for i in range(n_players):
        print(player_names[i] + get_space(player_names, player_names[i], ' ', 1) + ': ' + str(list_[i]))

def poker():
    n_players = int(input('Number Of Players?... '))
    player_names = [input(f'Player # {n+1} .... ') for n in range(n_players)]

    balances = [90] * n_players
    bids = [10] * n_players
    fold_code = [0] * n_players

    total = sum(bids)

    input('Press Enter To Continue...')
    print('Starting Game...')

    this_round = ''

    while True:
        print('Current Balances:')
        list_players(player_names, n_players, balances)
        print('Current Bids:')
        list_players(player_names, n_players, bids)

        if this_round == 'y':
            print('Who Won?')
            winner = input()

            balances[player_names.index(winner)] += total

            print()
            print(winner, 'Won This Round')
            print()

            print('Current Balances:')
            list_players(player_names, n_players, balances)

            for var in range(n_players):
                if 0 in balances:
                    ind_0 = balances.index(0)

                    print(player_names[ind_0].upper(), 'Is Out...')

                    n_players -= 1
                    player_names.pop(ind_0)
                    bids.pop(ind_0)
                    balances.pop(ind_0)

            for player in player_names:
                balances[player_names.index(player)] -= 10

            bids = [10] * n_players
            fold_code = [0] * n_players

            print('Deducting 10$ from all players for the new round...')
            print()

            print('Current Balances:')
            list_players(player_names, n_players, balances)
            print()

            print('Current Bids:')
            list_players(player_names, n_players, bids)
            print()

            print('-' * 35)

        for player_index in range(n_players):
            player = player_names[player_index]

            if fold_code[player_names.index(player)] == 1:
                continue

            print('>' * 15, player.upper(), '>' * 15 )
            action = input()

            if action == 'c':
                bid = max(bids) - bids[player_index]
                print(player, 'Called...')

            elif action == 'r':
                print('Raise By?')
                r = int(input())
                bid = max(bids) - bids[player_index]
                print(player, 'Raised By', r)

            elif action == 'f':
               fold_code[player_names.index(player)] = 1
               bid = 0
               print(player, 'Folded For the round')

            else:
                bid = 0
                print(player, 'Checked...')

            balances[player_names.index(player)] -= bid
            bids[player_names.index(player)] += bid
            total = sum(bids)

            print()
            print('Current Balances:')
            list_players(player_names, n_players, balances)

            print()
            print('Current Biddings:')
            list_players(player_names, n_players, bids)

            print()
            print('Total Bid : ' + str(total))

            print()
            print()

            print('-' * 35)

        print('Round Over?')
        this_round = input()

print('\n' * 20)
poker()

If you have any suggestions or if you find any mistakes, please point them out in the comments.

Hope this helps!

\$\endgroup\$
  • 1
    \$\begingroup\$ Instead of calling print() / print(whatever), couldn't one just do print(\nwhatever)? \$\endgroup\$ – BruceWayne Nov 9 at 2:59
  • \$\begingroup\$ @BruceWayne I did think of that, but don't you think it looks a bit ugly? print('\nwhatever') I totes agree with you if you have to make the code shorter. \$\endgroup\$ – Srivaths Nov 9 at 4:00
  • \$\begingroup\$ Can you please refer me to the Python Doc that states that docstrings should be written using " " " instead of ' ' ' \$\endgroup\$ – Elbasel Nov 9 at 9:31
  • \$\begingroup\$ Of course! Here it is! It says For consistency, always use """triple double quotes""" around docstrings. \$\endgroup\$ – Srivaths Nov 9 at 9:35
  • 1
    \$\begingroup\$ @Srivaths yeah perhaps, it's OP's call, I just wanted to mention another option instead of doing empty prints. \$\endgroup\$ – BruceWayne Nov 9 at 15:13
3
\$\begingroup\$

There is a lot of room for improvement but I will stick to the index stuff.

Naming

Your function

def x_player(list_secondary, string, list_primary):
    '''
    returns the item from a list(list_primary) at
    the index of another string (string) in another list(list_secondary)
    '''

    return list_primary[list_secondary.index(string)]
  • has bad names for function and parameters. x_player suggest that it is about players but in fact it is an absolutely generic function. Even if used with players the qualifier x_ does not give any hint what is happening or returned. also what is list_primary and what is list_secondary? The parameter name string is without any semantic hint. From the perspective of the function it even worse as the function is not limited to string type keys.
  • has a bad docstring. the docstring is just a lengthy read of the code line below. A reader not familiar with your code has a hard time to understand the usage.

What if we change the names and the docstring to

def get_value(keys, key, values):
    '''
    fakes a dict()
    '''
    return values[keys.index(key)]

All of a sudden it is absolutely clear, that this is a completely generic function that looks up values in a parallel list and may be used for any key type.

Consistency

You use your fake dict x_player for bids and balances, but you do not use it for fold_code where you do fold_code[player_names.index(player)] explicitly. Also there are explicit usages balances[player_names.index(player)] and bids[player_names.index(player)]. Either you think fold_code[player_names.index(player)] is readable, then avoid to create a function. Or you think that is somewhat hard to read, then create the function and use it throughout the code.

For your dict-less implementation you have several lists of the same length. You first fill the key list player_names then you create the value lists inconsistently

balances = [ 90 for n in range(n_players)]
bids = [10 for n in range(len(player_names))]
fold_code = [ 0 for n in range(len(player_names)) ]

The first line I like the least, n_players is a helper for the input loop only. The real truth is player_names, the list lengths must match. So in terms of dependency to the real stuff your other lines are better. However thy are not pythonic in two ways. In python you nearly never loop over range(len(x)). So

bids = [10 for n in range(len(player_names))]

would read

bids = [10 for name in player_names]

which is less error prone. Also in python you use _ when you do not need the variables you somehow get.

bids = [10 for name in player_names]

would read

bids = [10 for _ in player_names]

Loops over player

You have a loop over player_names where you have multiple fake dict lookups for player attributes:

for player in player_names:
    # [...]
    if fold_code[player_names.index(player)] == 1:
        # [...]
    if action == 'c':
        bid = max(bids) - x_player(player_names, player, bids)

For this case your fake dict is not very efficient. Instead of retrieving player name and have many lookups you could also lookup the player index once

for player_idx, player in enumerate(player_names):
    # [...]
    if fold_code[player_idx] == 1:
        # [...]
    if action == 'c':
        bid = max(bids) - bids[player_idx]

That makes the code more readable.

\$\endgroup\$
  • \$\begingroup\$ Can you please elaborate why range(len(x)) is more prone to error? \$\endgroup\$ – Elbasel Nov 9 at 9:28
  • \$\begingroup\$ Off by 1 is a typical error on loops. Of course it is hard to get your simple loop wrong. But if you access an element you type the container name twice for i in range(len(container)) you access container[i]. When copy-pasting it is easy to get one name wrong. If you do for i, e in enumerate(container): you index/value tuple always matches. Or you may iterate over slices for i, e in enumerate(container[5:-5:2]):. enumerate() also works on generators, where len() is not available. Get used to use enumerate(). \$\endgroup\$ – stefan Nov 11 at 18:57

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.