home *** CD-ROM | disk | FTP | other *** search
- # pizza.py -- generate a random pizza order for MacHack
- #
- # By Eric S. Raymond <esr@thyrsus.com>, June 23 2000, for the MacHack 15 hack show.
-
- import random, string, sys, re
- from Tkinter import *
- from Dialog import *
-
- _classes = (
- 'plain',
- 'vegan',
- 'vegetarian',
- 'mega-meat',
- 'meat-purist',
- 'mixed',
- 'mostly-meat',
- )
-
- _meats = (
- "pepperoni",
- "sausage",
- "ham",
- "hamburger",
- "prosciutto",
- )
-
- _veggies = (
- "mushroom",
- "black olives",
- "green peppers",
- "onion",
- "broccoli",
- "cauliflower",
- "pineapple",
- "roasted red peppers",
- )
-
- _prefs = (
- "I love it",
- "It's OK",
- "Only a few of these",
- )
-
- _cheeses = (
- "cheese",
- "double cheese",
- )
-
- _spices = (
- "hot pepper",
- "garlic",
- )
-
- # Each entry consists of a pizza class as a key, the value consists of a tuple
- # of ingrediant tuples. Each ingrediabt tuple has as [0] member an ingredients
- # array, and as [1] member a range of valid counts for that ingredient.
- _rules = {
- 'plain': ((_cheeses, [1]),),
- 'vegan': ((_veggies, [4]),
- (_spices, [0,1,2])),
- 'vegetarian': ((_veggies, [3,4]),
- (_spices, [0,1,2]),
- (_cheeses, [1])),
- 'mega-meat': ((_meats, [2,3,4]),
- (_spices, [0,1,2]),
- (_cheeses, [1])),
- 'meat-purist': ((_meats, [1]),
- (_spices, [0,1]),
- (_cheeses, [1])),
- 'mixed': ((_meats, [1]),
- (_spices, [0,1,2]),
- (_cheeses, [1]),
- (_veggies, [2,3])),
- 'mostly-meat': ((_meats, [2,3]),
- (_spices, [0,1,2]),
- (_cheeses, [1]),
- (_veggies, [1])),
- }
-
- #
- # Global state
- #
- classnumbers = {}
-
- def compute_order():
- "Fill in missing classes from the order, then generate the order."
- global specified, excess
-
- # Fill in the catch-all classes
- classnumbers['mostly-meat'] = classnumbers['mixed'] = 0
- specified = reduce(lambda x, y: x + y, classnumbers.values())
- excess = total - specified
- classnumbers['mostly-meat'] = excess / 2
- classnumbers['mixed'] = excess - classnumbers['mostly-meat']
- # Generate a list of tuples,
- # each consisting of a type, a recipe count, and a recipe:
- order = []
- for key in classnumbers.keys():
- for i in range(classnumbers[key]):
- recipe = generate_pizza(key)
- recipe.sort()
- oldrecipes = list(map(lambda x: x[2], order))
- if recipe in oldrecipes:
- find = oldrecipes.index(recipe)
- order[find] = (key, order[find][1]+1, order[find][2])
- else:
- order.append((key, 1, recipe))
- return order
-
- def genreport(order):
- "Format the generated order as a text string."
- result = "Total: %d\n" % (total,)
- oldkey = None
- for (key, count, recipe) in order:
- if key != oldkey:
- result = result + key + " (" + `classnumbers[key]` + "):\n"
- oldkey = key
- result = result + (" %d with %s\n"%(count,string.join(recipe,", ")))
- return result
-
- def multiselect(number, array):
- "Random-select a tuple of exactly number items from a given string tuple."
- length = len(array)
- outlist = []
- while number > 0:
- trial = random.choice(array)
- if trial in outlist:
- continue
- else:
- outlist.append(trial)
- number = number - 1
- return outlist
-
- def generate_pizza(pizzaclass):
- "Generate a pizza of a given class using the _rules table."
- recipe = []
- for (ingredient, frequency) in _rules[pizzaclass]:
- recipe = recipe + multiselect(random.choice(frequency), ingredient)
- return recipe
-
- def get_number_or_percentage(prompt):
- "Get a number, or a percent scaled to the total if the input includes %."
- raw = string.strip(raw_input(prompt))
- if raw[-1] == "%":
- number = int(total * string.atoi(raw[:-1]) / 100.0)
- else:
- number = string.atoi(raw)
- return number
-
- def consolemode():
- global total
- print "Hello, pizza!"
- total = string.atoi(raw_input("How many pizzas ya want? "))
- classnumbers['plain'] = get_number_or_percentage("How many plain pizzas ya want? ")
- classnumbers['vegan'] = get_number_or_percentage("How many vegan pizzas ya want? ")
- classnumbers['vegetarian'] = get_number_or_percentage("How many vegetarian pizzas ya want? ")
- classnumbers['mega-meat'] = get_number_or_percentage("How many mega-meat pizzas ya want? ")
- classnumbers['meat-purist'] = get_number_or_percentage("How many meat purist pizzas ya want? ")
-
- order = compute_order()
-
- if specified > total:
- print "Bogus order, too many pizzas by %d." % (specified - total)
- raise SystemExit
-
- # Generate class numbers as a check
- print genreport(order)
-
- # TkInter support
-
- class ValidatedField(Frame):
- "Accept a string, decimal or hex value in a labeled field."
- def __init__(self, master, type, prompt, hook):
- Frame.__init__(self, master)
- self.type = type
- self.hook = hook
- self.newval = StringVar(master)
- self.L = Label(self, text=prompt, anchor=W, width=40)
- self.L.pack(side=LEFT)
- self.E = Entry(self, textvar=self.newval)
- self.E.pack({'side':'left', 'expand':YES, 'fill':X})
- self.E.bind('<Return>', self.handlePost)
- self.E.bind('<Enter>', self.handleEnter)
- self.newval.set(0)
- self.errorwin = None
- def handleEnter(self, event):
- self.E.bind('<Leave>', self.handlePost)
- def handlePost(self, event):
- if self.errorwin:
- return
- self.E.bind('<Leave>', lambda e: None)
- result = string.strip(self.newval.get())
- if self.type == "decimal":
- if not re.compile("[" + string.digits +"]+$").match(result):
- self.error_popup(title="PizzaHack Error",
- banner="PizzaHack Error",
- text="Decimal digits only, please")
- return
- apply(self.hook, (result,))
- def error_popup(self, title, banner, text):
- self.errorwin = Toplevel()
- self.errorwin.title(title)
- self.errorwin.iconname(title)
- Label(self.errorwin, text=banner).pack()
- Label(self.errorwin, text=text).pack()
- Button(self.errorwin, text="Done",
- command=lambda x=self.errorwin: Widget.destroy(x), bd=2).pack()
-
- def totalhook(number):
- "Callback to accept a pizza total from the GUI panel."
- global total
- total = string.atoi(number)
-
- def journalhook(number, ptype):
- "Callback to accept a pizza total from the GUI panel."
- global classnumbers
- raw = string.strip(number)
- if raw[-1] == "%":
- number = int(total * string.atoi(raw[:-1]) / 100.0)
- else:
- number = string.atoi(raw)
- classnumbers[ptype]=number
-
- def helpwin(title, banner, text):
- # help message window with a self-destruct button
- helpwin = Toplevel()
- helpwin.title(title)
- helpwin.iconname(title)
- Label(helpwin, text=banner).pack()
- textframe = Frame(helpwin)
- scroll = Scrollbar(textframe)
- helpwin.textwidget = Text(textframe, width=100, setgrid=TRUE)
- textframe.pack(side=TOP, expand=YES, fill=BOTH)
- helpwin.textwidget.config(yscrollcommand=scroll.set)
- helpwin.textwidget.pack(side=LEFT, expand=YES, fill=BOTH)
- scroll.config(command=helpwin.textwidget.yview)
- scroll.pack(side=RIGHT, fill=BOTH)
- helpwin.textwidget.insert(END, text);
- Button(helpwin, text='Done',
- command=lambda x=helpwin: Widget.destroy(x), bd=2).pack()
- textframe.pack(side=TOP)
-
- def reportwindow():
- "Display the computed order result."
- order = compute_order()
- if specified > total:
- Dialog(self,
- title = "PizzaHack problem",
- text = "Bogus order -- too many pizzas specified.",
- bitmap = 'error',
- default = 0,
- strings = ("Done",))
- else:
- helpwin(title="PizzaHack",
- banner="Your pizza order:",
- text=genreport(order))
-
- def tkmode():
- global classnumbers
- root = Tk()
- root.title("PizzaHack")
- Label(root, text="PizzaHack").pack(fill=X, expand=YES)
- ValidatedField(root, "decimal", "How many pizzas ya want?", totalhook).pack()
- for pizzatype in ("plain","vegan","vegetarian","mega-meat","meat-purist"):
- ValidatedField(root,
- "decimals",
- "How many %s pizzas ya want?" % (pizzatype,),
- lambda result, ptype=pizzatype, counts=classnumbers: \
- journalhook(result, ptype)).pack(fill=X, expand=YES)
- botframe = Frame(root).pack()
- Button(botframe,text="Generate order",command=reportwindow).pack(side=LEFT)
- Button(botframe,text="Done",command=root.destroy).pack(side=RIGHT)
- root.mainloop()
-
- if __name__ == "__main__":
- if len(sys.argv[1:]):
- consolemode()
- else:
- tkmode()
-