Ryan Barnes Ryan Barnes - 1 month ago 11
Python Question

Display a list of options that changes based upon conditions, and have user select one of those options

I'm a novice at writing python, and I'm trying to create a dynamic selection of options to the user, ask them what option they want, and then perform a function based upon what they choose.

My "game" is recreating the scene from Die Hard where they have to get 4 gallons into a 5 gallon jug.

The user starts with two bottles that have nothing in them, and water available. Because they are starting out, they should only have two options:

[1] Fill bottle A
[2] Fill bottle B
Select Option:


Assuming the code was right, and chose option 1 and filled bottle A, the next options they have are now:

[1] pour bottle A into bottle B
[2] Fill bottle B
[3] Empty bottle A
Select Option:


Here is my (probably horrible) code thus far:

class Bottles(object):
amount = 0
def __init__(self,name,capacity,amount):
self.name = name
self.capacity = capacity
self.amount = amount

def AddWater(self,increase):
if (self.amount + increase) > self.capacity:
self.amount = self.capacity
print("Overflow! {0}'s at max capacity ({1} gallons)").format(self.name,self.capacity)
else:
self.amount = self.amount + increase

def RemWater(self,decrease):
if (self.amount - decrease) < 0:
self.amount = 0
print("Empty! {0} is now empty!").format(self.name)
else:
self.amount = self.amount - decrease

def ShowOptions():
available_options = []
option_value = 1

print("Bottle A Amount: {0}").format(bottle_a.amount)
print("Bottle B Amount: {0}").format(bottle_b.amount)

print("Your options are as follows:")
if bottle_a.amount != bottle_a.capacity:
print("[{0}] Fill bottle A").format(option_value)
available_options.append(str(option_value))
option_value += 1

if bottle_b.amount != bottle_b.capacity:
print("[{0}] Fill bottle B").format(option_value)
available_options.append(str(option_value))
option_value += 1

if bottle_a.amount != bottle_a.capacity and bottle_b.amount > 0:
print("[{0}] Pour water in Bottle B into Bottle A").format(option_value)
option_value += 1

if bottle_b.amount != bottle_b.capacity and bottle_a.amount != 0:
print("[{0}] Pour water in Bottle A into Bottle B").format(option_value)
option_value += 1

if bottle_a.amount == 4 or bottle_b.amount == 4:
print("{0}] Defuse bomb.").format(option_value)
option_value += 1

bottle_a = Bottles("Bottle A",5,3) # 5 gallon bottle
bottle_b = Bottles("Bottle B",3,0) # 3 gallon bottle

ShowOptions()


What I'm having a hard time grasping is how to both ask for their selection, and then run that function without adding a whole bunch of extra option checking each time.

Answer

You could construct a list of pairs where first item is the text to display and second item is a function to execute if option is selected. Then you would have to code the functionality to decide the options only once and executing selected option would be trivial.

Here's an example on how the game could look with above approach:

class Bottle(object):
    def __init__(self,name,capacity,amount):
        self.name = name
        self.capacity = capacity
        self.amount = amount

    def fill(self):
        self.amount = self.capacity

    def empty(self):
        self.amount = 0

    def fill_from(self, bottle):
        amount = min(self.capacity - self.amount, bottle.amount)
        self.amount += amount
        bottle.amount -= amount

# Add all the possible options for given bottle to the list
def add_bottle_options(options, bottle, other):
    # Fill
    if bottle.amount != bottle.capacity:
        options.append(('Fill {}'.format(bottle.name), lambda: bottle.fill()))

    # Empty
    if bottle.amount:
        options.append(('Empty {}'.format(bottle.name), lambda: bottle.empty()))

    # Pour to
    if bottle.amount != bottle.capacity and other.amount:
        text = 'Pour water in {} into {}'.format(other.name, bottle.name)
        options.append((text, lambda: bottle.fill_from(other)))


bottle_a = Bottle("Bottle A", 5, 0)  # 5 gallon bottle
bottle_b = Bottle("Bottle B", 3, 0)  # 3 gallon bottle
ticking = True

def defuse():
    global ticking
    ticking = False

while ticking:
    print('Bottle A Amount: {}, capacity: {}'.format(bottle_a.amount,
                                                     bottle_a.capacity))
    print('Bottle B Amount: {}, capacity: {}'.format(bottle_b.amount,
                                                     bottle_b.capacity))

    print("Your options are as follows:")

    # List of option text, function to execute tuples
    available_options = []
    add_bottle_options(available_options, bottle_a, bottle_b)
    add_bottle_options(available_options, bottle_b, bottle_a)

    if bottle_a.amount == 4 or bottle_b.amount == 4:
        available_options.append(('Defuse bomb', defuse))

    # Enumerate will return (number, item) tuples where the number starts
    # from second parameter. Since items are (name, function) 
    # tuples themselves enumerate returns (number, (name, function))
    # tuples which we need to unpack so that we can use number and name
    # for printing. (s, _) does nested unpacking so that name goes to
    # variable s and function to _ which is a common name for throwaway data.
    for i, (s, _) in enumerate(available_options, 1):
        print('[{}] {}'.format(i, s))

    try:
        # Ask user choice, convert it to int, subtract 1 since displayed
        # options use 1-based indexing and execute the selected option
        choice = input('Select option: ')
        available_options[int(choice) - 1][1]()
    except:
        print('Invalid option')

print('Congrats, you defused bomb')