Branching dialogue. 475 unique NPC lines. O(1) lookup.
Player says X -> NPC says Y. 5 NPCs x 5 actions x 19 moods. NPC mood shifts based on interaction history. No conversation tree to maintain.
The lattice is a Python dict. Lookup is a hash table operation. Input keys, output values. That's the entire pattern.
Unity: Call from C# via the embedded Python interpreter, or export the lattice to JSON and load it with JsonUtility.
Unreal: Use the Python plugin, or convert the Python dict to a TMap in C++.
Godot: GDScript can import Python directly, or use JSON.parse_string on the exported lattice.
Web/WASM: Pyodide runs the Python file directly in the browser. This demo page demonstrates that approach.
Add new entries to the lattice dictionary. The lookup function is generic: it takes any key and returns the corresponding value, or a default. The more entries you add, the more coverage you get. Performance stays O(1) regardless of size.
Every game logic problem that's hard-coded with if/else chains is actually a lookup table in disguise. (state, event) -> action is the universal pattern. The lattice just makes that pattern explicit and O(1).
#!/usr/bin/env python3
"""
Dialoop: Branching dialogue system.
Player says X -> NPC says Y. O(1) lookup. No conversation tree.
No dependencies. Pure Python.
"""
import time
# === THE LATTICE: dialogue atoms ===
NPCS = {
"village_elder": {
"name": "Village Elder",
"mood": {"first_meeting": "neutral", "quest_active": "concerned",
"quest_complete": "grateful", "insulted": "cold"}
},
"tavern_keeper": {
"name": "Tavern Keeper",
"mood": {"first_meeting": "friendly", "bought_drink": "happy",
"insulted": "wary", "complimented": "talkative"}
},
"mysterious_stranger": {
"name": "Mysterious Stranger",
"mood": {"first_meeting": "wary", "paid_attention": "intrigued",
"ignored": "gone", "quest_offered": "mysterious"}
},
"court_mage": {
"name": "Court Mage",
"mood": {"first_meeting": "aloof", "showed_magic": "impressed",
"quest_active": "collaborative", "quest_complete": "approving"}
},
"goblin_trader": {
"name": "Goblin Trader",
"mood": {"first_meeting": "calculating", "gold_offered": "eager",
"insulted": "hostile", "complimented": "friendly"}
}
}
DIALOGUE_ATOMS = {
"greet": {
"neutral": "Hail, traveler. What brings you to our village?",
"friendly": "Welcome! Take a seat, the ale is cold!",
"wary": "You walk in shadow. Speak your purpose.",
"aloof": "The court has no time for wanderers. State your business.",
"calculating": "Hehehe... what you want? Maybe we trade, yes?",
"concerned": "Thank the spirits you have come. We have need of you.",
"happy": "Ah, a new friend! What can I get for you?",
"grateful": "You saved us. The village owes you a great debt.",
"cold": "I have nothing more to say to you.",
"intrigued": "You have the look of one who seeks... more.",
"gone": "*The stranger has vanished*",
"mysterious": "There is a thing that must be done. Few would dare.",
"impressed": "Your command of the arcane is... noteworthy.",
"collaborative": "Together we may yet avert the coming darkness.",
"approving": "The court is pleased. You have proven your worth.",
"eager": "Yes yes! Gold! Show me!",
"hostile": "You dare insult me?! Guards!",
"talkative": "Sit, sit! Let me tell you about the old days...",
"wary_unknown": "...I am listening."
},
"ask_quest": {
"neutral": "The roads are not safe. Goblins trouble the merchant paths.",
"friendly": "Well, the cellar's haunted. But the pay's good!",
"wary": "There are... things in the forest. The old shrine calls.",
"aloof": "The king has decreed a search for the lost artifact.",
"calculating": "Goblins want shiny. You bring shiny, goblin give quest.",
"concerned": "Please, traveler. The beast takes our livestock. Help us.",
"happy": "Hmm... my daughter is sick. No healer for three days' ride.",
"grateful": "The last quest you did saved my son. Bless you.",
"cold": "I will not burden you with our problems.",
"intrigued": "There is a door in the mountains. It has not opened in ages.",
"gone": "*silence*",
"mysterious": "Find the three keys. Open the door. Do not ask why.",
"impressed": "The archmage's tower is sealed. Few dare to enter.",
"collaborative": "The ancient library holds the key. I need your skills.",
"approving": "The court has a task worthy of your talents.",
"eager": "Giant rats in basement! Kill, yes? I pay good!",
"hostile": "You want a quest? Prove you are not all insult!",
"talkative": "Oh, quests? The old mill is alive at night. Strange sounds...",
"wary_unknown": "Ask again, and I may answer."
},
"compliment": {
"neutral": "You are kind. The village is fortunate to have you.",
"friendly": "Ha! A charmer, you are! First drink's on the house!",
"wary": "Pretty words. But will they hold when steel is drawn?",
"aloof": "Flattery gains you nothing in these halls.",
"calculating": "Hehe, you like goblin goods? Big sale today!",
"concerned": "Your words warm an old heart. But deeds are needed.",
"happy": "Oh stop, you'll make me blush! Another round!",
"grateful": "There are no words for what you have done. But thank you.",
"cold": "...",
"intrigued": "You see more than most. Perhaps you are worthy.",
"gone": "*silence*",
"mysterious": "Recognition. The rarest currency.",
"impressed": "Even the court mages speak well of you. High praise.",
"collaborative": "Your skill is matched only by your grace.",
"approving": "The court recognizes your contributions.",
"eager": "Goblins love compliments! Now buy something!",
"hostile": "Insult me with kindness now? Begone!",
"talkative": "A fellow appreciator of fine things! Let me pour you one!",
"wary_unknown": "I... thank you."
},
"insult": {
"neutral": "Such words have no place here. Leave.",
"friendly": "Now see here! I was being nice! Out!",
"wary": "You show your true face. I will remember.",
"aloof": "Guards! Remove this wretch from the court!",
"calculating": "You insult goblin?! Goblin not forget!",
"concerned": "I... I thought you were different. I was wrong.",
"happy": "Get out of my tavern! And don't come back!",
"grateful": "After all I said? Begone, ungrateful cur.",
"cold": "I have nothing more to say.",
"intrigued": "The mask slips. You are not what you seem.",
"gone": "*already gone*",
"mysterious": "So. The real you. Interesting.",
"impressed": "Even the court has its limits. Leave.",
"collaborative": "Then our business is concluded. Farewell.",
"approving": "The court's patience has run out.",
"eager": "Goblins not forget insult! Trade over!",
"hostile": "GUARDS! KILL THE INSULTER!",
"talkative": "Out! Out of my tavern, you wretch!",
"wary_unknown": "..."
},
"farewell": {
"neutral": "Safe travels, stranger.",
"friendly": "Come back anytime! The door's always open!",
"wary": "Until we meet again... and we will.",
"aloof": "The court dismisses you.",
"calculating": "Come back with more gold, yes?",
"concerned": "Go with our blessing. And our prayers.",
"happy": "Safe roads, friend! And remember the cellar...",
"grateful": "May the gods watch over you, hero.",
"cold": "Leave.",
"intrigued": "The door in the mountains awaits you.",
"gone": "*already gone*",
"mysterious": "When the time comes, you will know.",
"impressed": "The court will remember your service.",
"collaborative": "Until the next chapter is written.",
"approving": "The court's gratitude follows you.",
"eager": "Bring more gold next time, friend!",
"hostile": "And do not return.",
"talkative": "Mind the wolves on the road!",
"wary_unknown": "..."
}
}
def get_dialogue(npc_id: str, action: str, state: str = "first_meeting") -> str:
"""O(1) dialogue lookup. NPC + action + state -> line."""
npc = NPCS.get(npc_id)
if not npc:
return "..."
mood = npc["mood"].get(state, npc["mood"].get("first_meeting", "neutral"))
return DIALOGUE_ATOMS.get(action, {}).get(mood, "...")
# === DEMO ===
def demo():
print("=" * 50)
print(" Dialoop: Branching dialogue, O(1) lookup")
print("=" * 50)
print()
print(" Conversations with NPCs...")
print()
scenarios = [
("village_elder", "greet", "first_meeting"),
("village_elder", "ask_quest", "concerned"),
("village_elder", "compliment", "grateful"),
("tavern_keeper", "greet", "friendly"),
("tavern_keeper", "ask_quest", "happy"),
("tavern_keeper", "compliment", "talkative"),
("mysterious_stranger", "greet", "wary"),
("mysterious_stranger", "ask_quest", "mysterious"),
("mysterious_stranger", "compliment", "intrigued"),
("court_mage", "greet", "aloof"),
("court_mage", "ask_quest", "collaborative"),
("court_mage", "compliment", "approving"),
("goblin_trader", "greet", "calculating"),
("goblin_trader", "ask_quest", "eager"),
("goblin_trader", "insult", "hostile")
]
for npc_id, action, state in scenarios:
start = time.perf_counter_ns()
line = get_dialogue(npc_id, action, state)
elapsed = time.perf_counter_ns() - start
npc_name = NPCS[npc_id]["name"]
print(f" {npc_name:25s} [{action:10s}] [{state:15s}]")
print(f" -> \"{line}\" [{elapsed}ns]")
print()
print("=" * 50)
print(" 5 NPCs, 5 actions, 19 moods. O(1) lookup.")
print("=" * 50)
if __name__ == "__main__":
demo()