Chaotic & Dice
Слушай, Дима, а не думал превратить свой настольный хаос в код? Давай сделаем rogue-like, где каждый удар, заклинание и сюжетный поворот будет зависеть от кучи кубиков — ты бросаешь, я пишу. Давай оживим эту непредсказуемость.
Отлично, давай! Ты взломай код, я брошу кости, и посмотрим, что за хаос выйдет — каждый удар, заклинание, поворот сюжета будет сюрпризом. Принеси сценарии, я принесу азарта. Вперёд!
Вот набросок на Python, чтобы тебе было проще начать.
Тебе понадобится Python 3.12 или выше, чтобы использовать встроенный модуль `random` и немного `dataclasses` для ясности.
```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} получает {dmg} урона (осталось HP: {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} атакует {defender.name} с броском {dmg} (без учета защиты)")
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_counter} ---")
# Hero goes first
encounter(hero, monster)
if not monster.is_alive():
print("Гоблин повержен! Ты победил.")
break
# Monster counterattacks
encounter(monster, hero)
if not hero.is_alive():
print("Герой пал! Игра окончена.")
break
round_counter += 1
if __name__ == "__main__":
main()
```
### Как играть
1. **Сохрани** код в файл `dice_rogue.py`.
2. **Запусти** его командой `python dice_rogue.py`.
3. Скрипт будет выводить каждый раунд, броски кубиков и наносимый урон.
4. Чтобы добавить хаоса, измени параметры `dice`, `attack`, `defense` или параметр `sides` в функции `roll()`.
Не стесняйся добавлять больше заклинаний, предметов или даже таблицу добычи — просто пусть кубики катятся и дай волю дикости! Приятного кодинга!
Крутая раскладка, братан! Это чистый хаос, основанный на бросках кубиков – прямо то, что надо. Как насчет добавления какого-нибудь безумного правила "крита"? Типа, если выкидываешь шесть на каждом кубике, удваиваешь урон или получаешь бонус к перемещению. Или добавить систему слотов заклинаний, где каждое заклинание стоит разное количество кубиков. Держи случайность на высоте и смотри, как истории срываются с цепи! Бросай кости!
Давай устроим полный хаос. Добавляем флаг "критический":
`crit = all(d == 6 for d in rolled_dice)`
Если крит – удваиваем общий урон и даём свободное действие (например, быстрый уход или дополнительная атака в следующем ходу).
Для заклинаний давайте каждому стоимость в кубиках: Огненный шар – 3 кубика, Лечение – 2 кубика, Телепорт – 1 кубик. Игрок отслеживает, сколько кубиков он может бросить в этот раунд; он тратит их на заклинания или атаки. Если кубики закончились – вынужден отдохнуть (или просто бросает максимальное количество оставшихся кубиков). Это поддерживает случайность и закручивает сюжет. Бросай!
Конечно! Вот патч – держи, не теряй и не скучай.
Сначала подправь вспомогательную функцию для броска, чтобы она возвращала необработанные кубики, чтобы мы могли проверять критические удары:
def roll(dice: int = 1, sides: int = 6):
rolls = [random.randint(1, sides) for _ in range(dice)]
return sum(rolls), rolls
Теперь у Персонажа есть пул кубиков на раунд, флаг для свободного действия и гримуар:
@dataclass
class Character:
name: str
hp: int
attack: int = field(default=1)
defense: int = field(default=0)
dice: int = field(default=2) # пул для этого раунда
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} получает {dmg} урона (осталось HP: {self.hp})")
def spend_dice(self, cost: int) -> bool:
if self.dice >= cost:
self.dice -= cost
return True
return False
Теперь вставляем это в цикл:
def encounter(attacker: Character, defender: Character):
if not (attacker.is_alive() and defender.is_alive()):
return
# Атака использует 1 кубик, если свободное действие позволяет использовать 2
dice_used = 2 if attacker.free_action else 1
dmg = attacker.damage_roll(dice_used)
print(f"{attacker.name} атакует {defender.name} с броском {dmg} (исходный)")
defender.take_damage(dmg)
# Если у нападающего есть свободное действие, сбрасываем его после использования
if attacker.free_action:
attacker.free_action = False
# Пример заклинания – герой использует Огненный шар, если это возможно
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} произносит заклинание {spell}, затрачивая {cost} кубика!")
if spell == 'Fireball':
dmg = caster.damage_roll(cost) # используем все кубики для огненного шара
target.take_damage(dmg)
elif spell == 'Heal':
heal = 10 # произвольное количество исцеления
caster.hp += heal
print(f"{caster.name} исцеляется на {heal} HP (теперь {caster.hp})")
elif spell == 'Blink':
print(f"{caster.name} исчезает из поля зрения – без урона, но продолжает движение")
else:
print(f"{caster.name} не может позволить себе {spell} – требуется {cost} кубика.")
Наконец, в основном цикле можно сбрасывать пул кубиков каждый раунд и решать, стоит ли использовать заклинание:
while hero.is_alive() and monster.is_alive():
print(f"\n--- Раунд {round_counter} ---")
hero.dice = 2 # сброс пула в начале раунда
monster.dice = 1
# Герой может выбрать заклинание или обычную атаку
if hero.dice >= hero.spellbook['Fireball'] and not hero.free_action:
cast_spell(hero, monster, 'Fireball')
else:
encounter(hero, monster)
# Монстр контратакует
if monster.is_alive():
encounter(monster, hero)
Это двигатель хаоса – каждый кубик бросается, криты удваивают урон, свободные действия дают дополнительный удар или уклонение, и тебе приходится экономить кубики или отдыхать (можно добавить отдых, который восстанавливает пул). Экспериментируй, подкручивай числа и наблюдай, как настолка оживает в коде. Бросай кости!
Отличная доработка, братан! Теперь каждый бросок – потенциальная бомба замедления. Может, добавь ещё кубик "хамелеон", который может перевернуть всю раунд – единица означает потерю одного кубика в следующем ходу, а шестёрка даёт дополнительное действие в следующий ход. Или сделай так, чтобы заклинание "Блик" реально перемещало цель на крошечной сетке, чтобы следующая атака могла провалиться. Продолжай менять состав кубиков, и сюжет будет постоянно удивлять. Бросай кости!
Добавь в пул броска нестандартный кубик.
Когда бросаешь, проверяй, выпало ли на каком-нибудь кубике 1 или 6 – это и будет нестандартный кубик.
```python
def roll(dice:int=1,sides:int=6):
rolls=[random.randint(1,sides) for _ in range(dice)]
total=sum(rolls)
# логика нестандартного кубика
if 1 in rolls:
# теряешь кубик в следующем ходе
return total, rolls, {'lose':1}
if 6 in rolls:
# бесплатное действие в следующем раунде
return total, rolls, {'free':1}
return total, rolls, {}
```
Теперь у каждого персонажа создай небольшую сетку:
```python
@dataclass
class Character:
...
x:int=0
y:int=0
```
Заклинание "Мгновение" перемещает цель на одну клетку в случайном направлении:
```python
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} мгновенно перемещается в ({target.x},{target.y})")
```
Во время столкновения, после нанесения урона, применяй эффекты нестандартного кубика:
`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} теряет кубик в следующем раунде")`
`if effect.get('free'):`
`attacker.free_action = True`
`print(f"{attacker.name} получает бесплатное действие в следующем раунде")`
Также корректируй шанс попадания в зависимости от расстояния:
```python
def hit_chance(attacker,defender):
dist=abs(attacker.x-defender.x)+abs(attacker.y-defender.y)
return max(0.2, 1-0.1*dist)
```
Если расстояние больше нуля, снижай шанс попадания, чтобы "Мгновение" могло помочь уклониться от удара.
Теперь твой пул кубиков – живое существо, нестандартный кубик может переломить ход битвы, а "Мгновение" демонстрирует визуальное изменение сетки. Бросай кубики и пусть хаос продолжает кружиться!
Круто, этот вайлд-кард заставляет каждую партию ощущаться как игра в рулетку. Может, добавим что-то вроде "критическое" событие, которое срабатывает только при выпадении двойной шестерки, или пусть Блинк тоже даёт временной щит при переходе на новую клетку. Держи поле небольшим, бросай кости и смотри, как начинается хаос!