import random
# ----------------------
# Data & Knowledge Base
# ----------------------
bean_types = {
"Arabica": {"acidity": "bright", "body": "medium", "flavor": ["fruity", "floral", "sweet"]},
"Robusta": {"acidity": "low", "body": "heavy", "flavor": ["nutty", "woody", "bitter"]},
"Liberica": {"acidity": "medium", "body": "full", "flavor": ["smoky", "spicy", "flowery"]},
"Excelsa": {"acidity": "high", "body": "light", "flavor": ["tart", "fruity", "complex"]}
}
roast_levels = {
"Light": {"acidity": "very high", "body": "light", "flavor_strength": "mild", "notes": ["citrus", "berry", "tea-like"]},
"Medium": {"acidity": "balanced", "body": "medium", "flavor_strength": "rich", "notes": ["chocolate", "caramel", "nut"]},
"Medium-Dark": {"acidity": "low", "body": "full", "flavor_strength": "strong", "notes": ["roasted", "toast", "dark chocolate"]},
"Dark": {"acidity": "very low", "body": "heavy", "flavor_strength": "intense", "notes": ["smoky", "bitter", "spicy"]}
}
grind_sizes = {
"Extra Fine": {"best_for": ["Espresso"], "extraction": "very fast", "risk": "bitter/over-extracted"},
"Fine": {"best_for": ["Espresso", "Moka Pot"], "extraction": "fast", "risk": "slightly bitter if too long"},
"Medium-Fine": {"best_for": ["Pour Over", "AeroPress"], "extraction": "balanced", "risk": "low"},
"Medium": {"best_for": ["Drip Coffee", "Chemex"], "extraction": "even", "risk": "low"},
"Coarse": {"best_for": ["French Press", "Cold Brew"], "extraction": "slow", "risk": "sour/under-extracted"},
"Extra Coarse": {"best_for": ["Cold Brew"], "extraction": "very slow", "risk": "watery"}
}
water_temp = {
"80°C - 85°C": {"effect": "milder, less extraction", "best_for": "light roasts, fruity beans"},
"86°C - 92°C": {"effect": "balanced extraction", "best_for": "most medium roasts"},
"93°C - 96°C": {"effect": "high extraction, bold flavors", "best_for": "dark roasts, heavy beans"},
"97°C - 100°C": {"effect": "over-extraction risk, bitter", "best_for": "none — use carefully"}
}
brew_ratios = {
"1:12": {"strength": "very strong", "body": "thick", "risk": "bitter"},
"1:15": {"strength": "strong", "body": "full", "risk": "balanced if brewed right"},
"1:18": {"strength": "balanced", "body": "medium", "risk": "ideal for most"},
"1:20": {"strength": "mild", "body": "light", "risk": "watery if too coarse"},
"1:22": {"strength": "very mild", "body": "thin", "risk": "weak flavor"}
}
brew_methods = {
"Pour Over": {"clarity": "high", "acidity": "emphasized", "body": "clean"},
"French Press": {"clarity": "low", "acidity": "mellowed", "body": "heavy/oily"},
"Espresso": {"clarity": "intense", "acidity": "concentrated", "body": "very thick"},
"Cold Brew": {"clarity": "smooth", "acidity": "very low", "body": "silky/sweet"},
"AeroPress": {"clarity": "medium-high", "acidity": "adjustable", "body": "balanced"},
"Moka Pot": {"clarity": "low", "acidity": "bold", "body": "syrupy"}
}
extras = {
"None": {"effect": "pure coffee taste"},
"Milk": {"effect": "softens acidity, adds creaminess, sweetens"},
"Sugar": {"effect": "masks bitterness, enhances sweetness"},
"Cinnamon": {"effect": "warm, spicy note, reduces sharpness"},
"Vanilla": {"effect": "sweet, aromatic, rounds out flavors"},
"Coconut": {"effect": "tropical, creamy, lightens body"}
}
# ----------------------
# Prediction Logic
# ----------------------
def predict_taste(bean, roast, grind, temp, ratio, method, extra):
# Base properties
base = bean_types[bean]
roast_props = roast_levels[roast]
grind_props = grind_sizes[grind]
temp_props = water_temp[temp]
ratio_props = brew_ratios[ratio]
method_props = brew_methods[method]
extra_props = extras[extra]
# Combine traits
acidity = []
body = []
flavors = []
notes = []
quality = 10 # start perfect, deduct for mismatches
# Acidity
acidity.append(base["acidity"])
acidity.append(roast_props["acidity"])
acidity.append(method_props["acidity"])
# Body
body.append(base["body"])
body.append(roast_props["body"])
body.append(ratio_props["body"])
body.append(method_props["body"])
# Flavors & Notes
flavors.extend(base["flavor"])
flavors.extend(roast_props["notes"])
notes.append(temp_props["effect"])
notes.append(grind_props["extraction"])
# Check for good/poor matches
if grind not in grind_props["best_for"]:
quality -= 3
notes.append(f"⚠️ Grind size not ideal for {method} — {grind_props['risk']}")
if temp == "97°C - 100°C" and roast != "Dark":
quality -= 2
notes.append("⚠️ Too hot for this roast — risk of bitterness")
if ratio == "1:12" and roast == "Light":
quality -= 2
notes.append("⚠️ Too concentrated for light roast — may taste harsh")
if extra != "None":
notes.append(f"✨ Added {extra}: {extra_props['effect']}")
# Final quality cap
quality = max(1, min(10, quality))
# Summarize
avg_acidity = max(set(acidity), key=acidity.count)
avg_body = max(set(body), key=body.count)
unique_flavors = list(set(flavors))[:4]
result = {
"acidity": avg_acidity,
"body": avg_body,
"flavor_profile": ", ".join(unique_flavors),
"characteristics": " ".join(notes),
"quality_score": quality,
"taste_prediction": get_taste_description(avg_acidity, avg_body, unique_flavors, quality)
}
return result
def get_taste_description(acidity, body, flavors, score):
flavor_str = ", ".join(flavors)
if score >= 9:
return f"š Excellent! A perfectly balanced cup — {acidity} acidity, {body} body, with clear notes of {flavor_str}. Smooth, rich, and enjoyable."
elif score >= 7:
return f"✅ Very good. {acidity.capitalize()} acidity, {body} body, tasting of {flavor_str}. Well-made and pleasant."
elif score >= 5:
return f"š Average. {acidity} acidity, {body} body, hints of {flavor_str}. Drinkable but could be adjusted for better balance."
else:
return f"⚠️ Needs improvement. {acidity} acidity, {body} body, muted {flavor_str}. Slightly off-balance — try changing grind or temperature."
# ----------------------
# Game Interface
# ----------------------
def coffee_lab_game():
print("="*50)
print("☕ COFFEE LAB SIMULATOR BOT ☕")
print("Experiment with your brew and see how it tastes!")
print("="*50)
# User inputs
bean = input(f"Bean type {list(bean_types.keys())}: ").strip().title()
while bean not in bean_types:
bean = input("Invalid choice. Try again: ").strip().title()
roast = input(f"Roast level {list(roast_levels.keys())}: ").strip().title()
while roast not in roast_levels:
roast = input("Invalid choice. Try again: ").strip().title()
grind = input(f"Grind size {list(grind_sizes.keys())}: ").strip().title()
while grind not in grind_sizes:
grind = input("Invalid choice. Try again: ").strip().title()
temp = input(f"Water temperature {list(water_temp.keys())}: ").strip()
while temp not in water_temp:
temp = input("Invalid choice. Try again: ").strip()
ratio = input(f"Brew ratio (coffee:water) {list(brew_ratios.keys())}: ").strip()
while ratio not in brew_ratios:
ratio = input("Invalid choice. Try again: ").strip()
method = input(f"Brew method {list(brew_methods.keys())}: ").strip().title()
while method not in brew_methods:
method = input("Invalid choice. Try again: ").strip().title()
extra = input(f"Add-in {list(extras.keys())}: ").strip().title()
while extra not in extras:
extra = input("Invalid choice. Try again: ").strip().title()
# Simulate & Predict
print("\nš Brewing your coffee...")
result = predict_taste(bean, roast, grind, temp, ratio, method, extra)
# Show Result
print("\n" + "="*50)
print("š TASTE PREDICTION RESULT")
print("="*50)
print(f"Acidity: {result['acidity']}")
print(f"Body: {result['body']}")
print(f"Flavor Profile: {result['flavor_profile']}")
print(f"Notes: {result['characteristics']}")
print(f"Quality Score: {result['quality_score']}/10")
print("\nš¬ Final Verdict:")
print(result['taste_prediction'])
print("="*50)
# Play again
again = input("\nWant to experiment again? (yes/no): ").strip().lower()
if again == "yes":
coffee_lab_game()
else:
print("Thanks for playing! Keep experimenting to find your perfect cup. ☕")
# Run the game
if __name__ == "__main__":
coffee_lab_game()