I am trying to create a character selection CLI using python.

I want to display the user's Current Party of Characters Selected so that they can see who they have already chosen. The user cannot choose the same character twice. A user must have 4 characters to proceed

I have created the following code (I do call it correctly don't worry!):

def ChooseCharacters():
    CharList=[]
    CurrentParty=[]
    print("Select up to 4 explorers!")
    while len(CharList)!=4:
        if len(CharList)!=4:
            if len(CharList)==0:
                pass
            else:
                for CharID in list(set(CharList)):
                    print(CharList)
                    SQL=("SELECT firstname,secondname FROM characters WHERE CharID=%s")
                    mycursor.execute(SQL,(CharID,))
                    myresult=mycursor.fetchone()
                    Name=(myresult[0]+" "+myresult[1])
                    CurrentParty= list(set(CurrentParty))
                    CurrentParty.append(Name)
                print("Current Party: ",CurrentParty)
        PrintChoice(CharList)
def PrintChoice(CharList):
    mycursor.execute("SELECT * FROM characters")
    myresults=mycursor.fetchall()
    Num=0
    for char in myresults:
        Num = Num + 1
        print(str(Num)+("."),char[1],char[2])
    Choice=input("Select Character: ")
    if Choice in CharList:
        print("Character already selected!")
        return
    CharList.append(Choice)
    print(CharList)

It appears that the first character chosen is duplicated in the list, the rest of the code works fine.

EXPECTED RESULT

Current Party:  ['Sam Jordan']

Current Party:  ['Sam Jordan','Olivia Cope']

Current Party:  ['Sam Jordan','Olivia Cope','Dylan Nesbitt']

ACTUAL RESULTS

Current Party:  ['Sam Jordan']

Current Party:  ['Olive Cope', 'Sam Jordan', 'Sam Jordan']

Current Party:  ['Sam Jordan', 'Olive Cope', 'Dylan Nesbitt', 'Sam Jordan']

I can't work out where the second replication of the first input occurs, seeing as the first input is outputs correctly.

If you need to see the database table just ask.

2 Answers

0
snakecharmerb On Best Solutions

The problem lies in the handling of the CurrentParty and CharList lists. At the beginning of the for loop that gets the names, CurrentParty contains the names that were added the last time a character was selected. The order of elements in CharList is potentially changing in each execution of the loop because of the set call, so it's possible that the last name added to CurrentParty is already there.

A simple solution might be to re-initialise CurrentParty for each execution of the loop, and remove the unnecessary set-ifying of CharList. A simplified version of the function might be:

def ChooseCharacters():
    CharList = []
    print("Select up to 4 explorers!")
    while len(CharList) < 4:
        PrintChoice(CharList)
        CurrentParty = []
        for CharID in CharList:
            SQL = "SELECT firstname,secondname FROM characters WHERE CharID=%s"
            mycursor.execute(SQL, (CharID,))
            myresult = mycursor.fetchone()
            Name = myresult[0] + " " + myresult[1]
            CurrentParty.append(Name)
        print("Current Party: ", CurrentParty)
    print("Final Party: ", CurrentParty)
    print()

As user FMc observes in their answer, the code in the question is difficult to understand. Here's a version of the code that tries to reduce duplication, keep clear responsibilities for each function, and minimise calls to the database. For simplicity, it assumes that mycursor exists as a global variable.

This code was written using Python 3.7; if you are using an earlier version of Python make all_chars an OrderedDict to ensure the correct behaviour.

def main():
    # Call this function to run the code.
    all_chars = fetch_characters()
    selected = select_characters(all_chars)
    party_ids = map_selection_to_ids(selected, all_chars)
    print_party(party_ids, all_chars)
    # Do rest of the program.
    return


def fetch_characters():
    # Make a dictionary that maps database id to character name.
    all_chars = {}
    stmt = """SELECT CharID, firstname, secondname FROM characters;"""
    mycursor.execute(stmt)
    for id_, firstname, secondname in mycursor.fetchall():
        all_chars[id_] = '{} {}'.format(firstname, secondname)
    return all_chars


def select_characters(all_chars):
    # Display the selection menu.
    # Note the values that the user selects will not necessarily 
    # correspond to the database id.
    selected = set()
    while len(selected) < 4:
        for idx, name in enumerate(all_chars.values(), start=1):
            print('{}. {}'.format(idx, name))
        print()
        choice = input('Select Character: ')
        if choice in selected:
            print("Character already selected!")
            continue
        selected.add(choice)
    return selected


def map_selection_to_ids(selected, all_chars):
    # Given the values selected by the user, get the corresponding 
    # database id.
    party_ids = []
    char_ids = list(all_chars.keys())
    for selection in selected:
        adjusted = int(selection) - 1
        party_ids.append(char_ids[adjusted])
    return party_ids


def print_party(party_ids, all_chars):
    names = []
    for id_ in party_ids:
        names.append(all_chars[id_])
    print(', '.join(names))
    return
1
FMc On

Your current code is a bit hard to follow because it comingles very different types of activity: reading from a DB, having a user make choices interactively, and regular algorithmic logic. In addition, the code relies on a pattern that is generally best to avoid when possible -- namely, passing data structures to other functions so that the other functions can modify them. It's usually simpler and advisable to write functions that take data and return new data.

Rather than puzzle over how to fix your current code, I would suggest that you refactor it to make some cleaner separations. Here's a quick illustration:

import string

# A top-level function to orchestrate things.
def choose_characters():
    chars = get_characters_from_db(12)  # Read 12 from DB.
    selected = select_items(chars, 4)   # Select 4 of them.
    print(selected)

# One or more functions to interact with the database.
# In this example we just return some mock data.
def get_characters_from_db(n):
    return string.ascii_uppercase[0:n]

# A general function to have a user select N items.
def select_items(xs, n):
    selected = []
    print('Choices:')
    for i, x in enumerate(xs):
        print(i + 1, x)
    print()
    while len(selected) < n:
        s = input('=> ')
        try:
            i = int(s) - 1
            x = xs[i]
        except Exception:
            print('Invalid entry')
            continue
        if x in selected:
            print('Already selected')
        else:
            selected.append(x)
    print()
    return selected

choose_characters()