Damage calculator. 12 affinities x 5 crit tiers.
ATK x DEF x Affinity x Crit = Damage. 12-element affinity system with 2x/0.5x modifiers. 5 crit tiers with 5-50% chance. O(1) per hit.
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
"""
BattleCalc: Damage calculator.
Attack x Defense x Affinity x Crit = Damage. O(1) per hit.
No dependencies. Pure Python.
"""
import time
import random
# === THE LATTICE: damage matrix ===
AFFINITY = {
"fire": {"strong": ["nature", "ice"], "weak": ["water", "earth"], "neutral": ["lightning", "holy", "dark", "arcane", "physical"]},
"ice": {"strong": ["nature", "fire"], "weak": ["fire", "lightning"], "neutral": ["water", "earth", "holy", "dark", "arcane", "physical"]},
"lightning": {"strong": ["water", "earth"], "weak": ["earth", "lightning"], "neutral": ["fire", "ice", "holy", "dark", "arcane", "physical"]},
"holy": {"strong": ["dark", "shadow"], "weak": ["dark", "arcane"], "neutral": ["fire", "ice", "water", "earth", "lightning", "physical"]},
"dark": {"strong": ["holy", "arcane"], "weak": ["holy", "light"], "neutral": ["fire", "ice", "water", "earth", "lightning", "physical"]},
"earth": {"strong": ["lightning", "ice"], "weak": ["water", "wind"], "neutral": ["fire", "holy", "dark", "arcane", "physical"]},
"water": {"strong": ["fire", "earth"], "weak": ["lightning", "ice"], "neutral": ["holy", "dark", "arcane", "physical"]},
"wind": {"strong": ["earth", "lightning"], "weak": ["ice", "earth"], "neutral": ["fire", "holy", "dark", "arcane", "physical"]},
"nature": {"strong": ["water", "earth"], "weak": ["fire", "ice"], "neutral": ["lightning", "holy", "dark", "arcane", "physical"]},
"arcane": {"strong": ["holy", "dark"], "weak": ["dark", "physical"], "neutral": ["fire", "ice", "water", "earth", "lightning"]},
"shadow": {"strong": ["holy", "light"], "weak": ["holy", "light"], "neutral": ["fire", "ice", "water", "earth", "lightning", "physical"]},
"physical": {"strong": ["arcane"], "weak": ["arcane"], "neutral": ["fire", "ice", "water", "earth", "lightning", "holy", "dark"]}
}
CRIT_TIERS = {
"common": {"chance": 0.05, "mult": 1.5},
"uncommon": {"chance": 0.10, "mult": 2.0},
"rare": {"chance": 0.20, "mult": 2.5},
"epic": {"chance": 0.30, "mult": 3.0},
"legendary": {"chance": 0.50, "mult": 4.0}
}
def affinity_modifier(attacker: str, defender: str) -> float:
"""O(1) affinity modifier lookup."""
if attacker not in AFFINITY:
return 1.0
if defender in AFFINITY[attacker]["strong"]:
return 2.0
if defender in AFFINITY[attacker]["weak"]:
return 0.5
return 1.0
def calc_damage(attacker_atk: int, defender_def: int, attacker_aff: str, defender_aff: str, crit_tier: str = "common") -> dict:
"""O(1) damage calculation."""
base = max(1, attacker_atk - defender_def // 2)
aff_mod = affinity_modifier(attacker_aff, defender_aff)
crit_data = CRIT_TIERS.get(crit_tier, CRIT_TIERS["common"])
crit_roll = random.random()
is_crit = crit_roll < crit_data["chance"]
crit_mult = crit_data["mult"] if is_crit else 1.0
final = int(base * aff_mod * crit_mult)
return {
"base": base,
"affinity_modifier": aff_mod,
"is_crit": is_crit,
"crit_mult": crit_mult,
"final": max(1, final),
"effect": "CRITICAL HIT!" if is_crit else None
}
def describe_affinity(attacker: str, defender: str) -> str:
"""O(1) affinity matchup description."""
mod = affinity_modifier(attacker, defender)
if mod == 2.0:
return f"{attacker.upper()} is SUPER EFFECTIVE against {defender}!"
if mod == 0.5:
return f"{attacker.upper()} is WEAK against {defender}."
return f"{attacker.upper()} is neutral against {defender}."
# === DEMO ===
def demo():
print("=" * 50)
print(" BattleCalc: Damage calculator, O(1) per hit")
print("=" * 50)
print()
print(" Combat scenario: Hero vs Dragon")
print(" Hero: ATK 50, AFF fire, Sword (epic crit)")
print(" Dragon: DEF 30, AFF ice")
print()
scenarios = [
("fire", "ice", 2.0, "super effective"),
("fire", "water", 0.5, "resisted"),
("fire", "fire", 1.0, "neutral"),
("holy", "dark", 2.0, "super effective"),
("dark", "holy", 0.5, "resisted"),
("ice", "fire", 0.5, "resisted"),
("lightning", "water", 2.0, "super effective"),
("shadow", "holy", 0.5, "resisted")
]
for aff_a, aff_d, expected, desc in scenarios:
start = time.perf_counter_ns()
mod = affinity_modifier(aff_a, aff_d)
elapsed = time.perf_counter_ns() - start
result = "OK" if mod == expected else "MISMATCH"
print(f" {aff_a:10s} vs {aff_d:10s} = x{mod} ({desc:15s}) [{elapsed}ns] [{result}]")
print()
print(" Battle rounds:")
print()
for i in range(8):
start = time.perf_counter_ns()
result = calc_damage(attacker_atk=50, defender_def=30, attacker_aff="fire", defender_aff="ice", crit_tier="epic")
elapsed = time.perf_counter_ns() - start
crit_str = " [CRIT!]" if result["is_crit"] else ""
print(f" Round {i+1}: {result['final']:4d} damage{crit_str} (base:{result['base']} x{result['affinity_modifier']} x{result['crit_mult']}) [{elapsed}ns]")
print()
print("=" * 50)
print(" 12 affinities, 5 crit tiers. O(1) per hit. No formulas.")
print("=" * 50)
if __name__ == "__main__":
demo()