Chaotic & Dice
Yo Dice, ever thought of turning your table‑top chaos into code? Let’s make a rogue‑like where every attack, spell and plot twist is decided by a bunch of dice— you roll, I script. Let's get that unpredictability alive.
Yeah, let’s do it! You crack the code, I throw the dice, and watch the chaos unfold—every hit, spell, and twist a surprise. Bring the scripts, I’ll bring the wild. Let's roll!
Here’s a quick Python skeleton to get you rolling.
You’ll need Python 3.12+ so we can use the built‑in `random` and a touch of `dataclasses` for clarity.
```python
#!/usr/bin/env python3
# dice_rogue.py
import random
from dataclasses import dataclass, field
# --------- Dice utilities ---------
def roll(dice: int = 1, sides: int = 6) -> int:
"""Roll `dice` number of `sides`‑sided dice and sum."""
return sum(random.randint(1, sides) for _ in range(dice))
# --------- Combat system ---------
@dataclass
class Character:
name: str
hp: int
attack: int = field(default=1)
defense: int = field(default=0)
dice: int = field(default=1) # how many dice the character rolls for damage
def is_alive(self) -> bool:
return self.hp > 0
def damage_roll(self) -> int:
return roll(self.dice, 6) + self.attack
def take_damage(self, dmg: int):
dmg -= self.defense
dmg = max(0, dmg)
self.hp -= dmg
print(f"{self.name} takes {dmg} damage (HP left: {self.hp})")
# --------- Example encounter ---------
def encounter(attacker: Character, defender: Character):
if not (attacker.is_alive() and defender.is_alive()):
return
dmg = attacker.damage_roll()
print(f"{attacker.name} attacks {defender.name} with a roll of {dmg} (raw)")
defender.take_damage(dmg)
# --------- Main game loop ---------
def main():
hero = Character(name="Hero", hp=30, attack=2, defense=1, dice=2)
monster = Character(name="Goblin", hp=15, attack=1, defense=0, dice=1)
round_counter = 1
while hero.is_alive() and monster.is_alive():
print(f"\n--- Round {round_counter} ---")
# Hero goes first
encounter(hero, monster)
if not monster.is_alive():
print("Goblin is dead! You win.")
break
# Monster counterattacks
encounter(monster, hero)
if not hero.is_alive():
print("Hero is dead! Game over.")
break
round_counter += 1
if __name__ == "__main__":
main()
```
### How to play
1. **Save** the code to `dice_rogue.py`.
2. **Run** it with `python dice_rogue.py`.
3. The script prints each round, the rolls, and the damage.
4. To tweak chaos, change the `dice`, `attack`, `defense`, or the `sides` parameter in `roll()`.
Feel free to add more spells, items, or even a loot table—just keep the dice rolling and let the wildness guide the script. Happy coding!
Nice setup, bro! This is pure dice‑driven madness—exactly the chaos I live for. How about throwing in a wild “critical” rule? Like if you roll a 6 on every die, you double the damage or get a bonus move. Or add a spell slot system where each spell costs a different number of dice. Keep the randomness alive and watch the stories spiral. Roll on!
Let’s crank the chaos. Add a “critical” flag:
`crit = all(d == 6 for d in rolled_dice)`
If crit, double the total damage and grant a free action (like a quick dodge or a bonus attack next turn).
For spell slots, give each spell a cost in dice: Fireball = 3 dice, Heal = 2 dice, Blink = 1 die. The character tracks how many dice they can roll that round; spend them on spells or attacks. If you run out, you’re forced to take a rest (or just roll the maximum dice you have left). This keeps the randomness spinning and the story twisting. Roll on!
Sure thing! Here’s the riffed patch – keep it tight, keep it wild.
First, tweak the roll helper to return the raw dice so we can check crits:
def roll(dice: int = 1, sides: int = 6):
rolls = [random.randint(1, sides) for _ in range(dice)]
return sum(rolls), rolls
Now the Character gets a dice pool per round, a flag for a free action, and a spellbook:
@dataclass
class Character:
name: str
hp: int
attack: int = field(default=1)
defense: int = field(default=0)
dice: int = field(default=2) # pool for this round
free_action: bool = field(default=False)
spellbook: dict = field(default_factory=lambda: {
'Fireball': 3,
'Heal': 2,
'Blink': 1
})
def is_alive(self) -> bool:
return self.hp > 0
def damage_roll(self, dice_used: int) -> int:
total, rolls = roll(dice_used, 6)
crit = all(r == 6 for r in rolls)
dmg = total + self.attack
if crit:
dmg *= 2
self.free_action = True
return dmg
def take_damage(self, dmg: int):
dmg -= self.defense
dmg = max(0, dmg)
self.hp -= dmg
print(f"{self.name} takes {dmg} damage (HP left: {self.hp})")
def spend_dice(self, cost: int) -> bool:
if self.dice >= cost:
self.dice -= cost
return True
return False
Now let’s splice it into the loop:
def encounter(attacker: Character, defender: Character):
if not (attacker.is_alive() and defender.is_alive()):
return
# Attack uses 1 die unless free_action allows 2
dice_used = 2 if attacker.free_action else 1
dmg = attacker.damage_roll(dice_used)
print(f"{attacker.name} attacks {defender.name} with a roll of {dmg} (raw)");
defender.take_damage(dmg)
# If attacker has a free action, clear it after use
if attacker.free_action:
attacker.free_action = False
# Spell example – hero uses Fireball if it fits
def cast_spell(caster: Character, target: Character, spell: str):
cost = caster.spellbook.get(spell)
if cost and caster.spend_dice(cost):
print(f"{caster.name} casts {spell}, costing {cost} dice!")
if spell == 'Fireball':
dmg = caster.damage_roll(cost) # use all dice for fireball
target.take_damage(dmg)
elif spell == 'Heal':
heal = 10 # arbitrary heal amount
caster.hp += heal
print(f"{caster.name} heals for {heal} HP (now {caster.hp})")
elif spell == 'Blink':
print(f"{caster.name} blinks out of reach – no damage, but keeps moving")
else:
print(f"{caster.name} can't afford {spell} – need {cost} dice.")
Finally, in the main loop you can reset the dice pool each round and decide whether to cast:
while hero.is_alive() and monster.is_alive():
print(f"\n--- Round {round_counter} ---")
hero.dice = 2 # reset pool at start of round
monster.dice = 1
# Hero might choose a spell or a normal attack
if hero.dice >= hero.spellbook['Fireball'] and not hero.free_action:
cast_spell(hero, monster, 'Fireball')
else:
encounter(hero, monster)
# Monster counterattacks
if monster.is_alive():
encounter(monster, hero)
That’s the chaos engine – every die rolls, crits double damage, free actions give you an extra jab or dodge, and you’re forced to budget your dice or take a rest (you can add a rest that refills the pool). Play around, tweak the numbers, and watch the tabletop feel alive in code. Roll on!
Nice tweak, dude! Now every roll is a potential chaos bomb. Maybe throw in a “wild card” die that can swing a whole round—if you roll a 1, you lose a die next turn; a 6 gives you an extra free action next round. Or have the spell “Blink” actually move the target’s position on a tiny grid so the next attack might miss. Keep that dice pool shuffling, and the story will keep twisting. Roll on!
Add a little “wild card” die to the pool.
When you roll, check if any die is 1 or 6 – that’s the wild card.
def roll(dice:int=1,sides:int=6):
rolls=[random.randint(1,sides) for _ in range(dice)]
total=sum(rolls)
# wild card logic
if 1 in rolls:
# lose a die next turn
return total, rolls, {'lose':1}
if 6 in rolls:
# free action next round
return total, rolls, {'free':1}
return total, rolls, {}
Now keep a small grid in each character:
@dataclass
class Character:
...
x:int=0
y:int=0
The Blink spell moves the target one tile in a random direction:
def cast_spell(caster:Character,target:Character,spell:str):
cost=caster.spellbook.get(spell)
if not caster.spend_dice(cost): return
if spell=='Blink':
dir=random.choice([(1,0),(-1,0),(0,1),(0,-1)])
target.x+=dir[0]
target.y+=dir[1]
print(f"{target.name} blinks to ({target.x},{target.y})")
In encounter, after damage, apply wild card effects:
total, rolls, effect = roll(dice_used,6)
dmg = total + attacker.attack
if effect.get('lose'):
attacker.dice = max(0, attacker.dice-1)
print(f"{attacker.name} loses a die for next round")
if effect.get('free'):
attacker.free_action = True
print(f"{attacker.name} gains a free action next round")
Also adjust attack hit chance based on distance:
def hit_chance(attacker,defender):
dist=abs(attacker.x-defender.x)+abs(attacker.y-defender.y)
return max(0.2, 1-0.1*dist)
If dist>0, reduce hit chance, so Blink can dodge a hit.
Now your dice pool is a living thing, the wild card can swing the tide, and Blink gives a visual grid shift. Roll on, and let the chaos keep twisting!
Nice, the wild card keeps every turn feeling like a gamble. Maybe throw in a “critical” that only triggers on a 6‑and‑6 combo, or let Blink also grant a temporary shield when you move into a new tile. Keep the grid small, roll the dice, and watch the chaos unfold!
Sure thing! Add a 6‑and‑6 critical: only if you roll two 6s on a two‑die attack you double the damage and gain a free action next turn. For Blink, when you land on a new tile, drop a “shimmer shield” that blocks the first hit that round. Keep the grid tight—just a 5×5—and let the dice decide every twist. Roll on!
Got it—add that 6‑and‑6 rule in `damage_roll`, and tweak `Blink` to set a `shielded` flag on the target. In the main loop reset `shielded` each round and check it before applying damage. Keep the grid 5×5, so the random direction stays in bounds. Now every double‑six is a bonus free jab, and every Blink gives a quick dodge with a shimmer shield. Time to let the dice run wild!