Files
ProgressQuest-cli/pqcli/mechanic.py
T
2018-11-05 21:04:39 +01:00

264 lines
6.9 KiB
Python

import datetime
import enum
import random
import typing as T
from dataclasses import dataclass, field
from pqcli.config import *
def level_up_time(level: int) -> int:
# seconds
return 20 * level * 60
def random_low(below):
return min(random.randint(0, below), random.randint(0, below))
def generate_name() -> str:
parts = [
"br|cr|dr|fr|gr|j|kr|l|m|n|pr||||r|sh|tr|v|wh|x|y|z".split("|"),
"a|a|e|e|i|i|o|o|u|u|ae|ie|oo|ou".split("|"),
"b|ck|d|g|k|m|n|p|t|v|x|z".split("|"),
]
result = ""
for i in range(6):
result += random.choice(parts[i % 3])
return result.title()
@dataclass
class Stats:
def __init__(self) -> None:
self.values = {stat: 0 for stat in Stat}
def __iter__(self) -> T.Iterable[T.Tuple[Stat, int]]:
return iter(self.values.items())
def __setitem__(self, stat: Stat, value: int) -> None:
self.values[stat] = value
def __getitem__(self, stat: Stat) -> int:
return self.values[stat]
@dataclass
class Item:
name: str
quantity: int
@dataclass
class Spell:
name: str
level: int
class Inventory:
def __init__(self, items: T.Optional[T.List[Item]] = []) -> None:
self.items = [] if items is None else items
def add(self, item_name: str, quantity: int) -> None:
for item in self.items:
if item.name == item_name:
item.quantity += quantity
break
else:
self.items.append(Item(name=item_name, quantity=quantity))
class SpellBook:
def __init__(self, spells: T.Optional[T.List[Item]] = None) -> None:
self.spells = [] if spells is None else spells
def add(self, spell_name: str, level: int) -> None:
for item in self.spells:
if item.name == spell_name:
item.level += level
break
else:
self.spells.append(Spell(name=spell_name, level=level))
class Bar:
def __init__(self, max_: int, position: int = 0) -> None:
self.position = position
self.max_ = max_
def reset(self, new_max: int, position: int = 0) -> None:
self.max_ = new_max
self.position = position
class TaskType(enum.Enum):
task = "task"
plot = "plot"
@dataclass
class Task:
task_type: TaskType
length: int
description: str
class Player:
def __init__(
self,
name: str,
birthday: datetime.datetime,
race: Race,
class_: Class,
stats: Stats,
) -> None:
self.name: str = name
self.birthday: datetime.datetime = birthday
self.race: Race = race
self.class_: Class = class_
self.stats: Stats = stats
self.level = 1
self.act = 0
self.queue: T.List[Task] = [
Task(
TaskType.task,
10,
"Experiencing an enigmatic and foreboding night vision",
),
Task(
TaskType.task,
6,
"Much is revealed about that wise old bastard you'd underestimated",
),
Task(
TaskType.task,
6,
"A shocking series of events leaves you alone and bewildered, but resolute",
),
Task(
TaskType.task,
4,
"Drawing upon an unrealized reserve of determination, you set out on a long and dangerous journey",
),
Task(TaskType.plot, 2, "Loading"),
]
self.exp_bar = Bar(max_=level_up_time(1))
self.encum_bar = Bar(max_=stats[Stat.strength] + 10)
self.quest_bar = Bar(max_=1)
self.task_bar = Bar(max_=2000)
self.plot_bar = Bar(max_=sum(task.length for task in self.queue))
self.inventory = Inventory([Item(name="Gold", quantity=0)])
self.spell_book = SpellBook()
self.equipment: T.Dict[Equipment, str] = {
Equipment.weapon: "Sharp Rock",
Equipment.hauberk: "-3 Burlap",
}
def level_up(self) -> None:
self.level += 1
self.stats[Stat.hp_max] += (
self.stats[Stat.condition] // 3 + 1 + random.randint(0, 3)
)
self.stats[Stat.mp_max] += (
self.stats[Stat.intelligence] // 3 + 1 + random.randint(0, 3)
)
self.win_stat()
self.win_stat()
self.win_spell()
self.exp_bar.reset(level_up_time(self.level))
def win_stat(self) -> bool:
if random.choice([False, True]):
chosen_stat = random.choice(list(Stat))
else:
# favor the beststat so it will tend to clump
t = sum(value ** 2 for _stat, value in self.stats)
t = random.randint(0, t - 1)
chosen_stat: Stat = None
for stat, value in self.stats:
chosen_stat = stat
t -= value ** 2
if t < 0:
return False
self.stats[chosen_stat] += 1
if chosen_stat == Stat.strength:
self.encum_bar.reset(
10 + self.stats[Stat.strength], self.encum_bar.position
)
return True
def win_spell(self) -> None:
self.spell_book.add(
SPELLS[
random_low(
min(
self.stats[Stat.wisdom] + self.level - 1,
len(SPELLS) - 1,
)
)
],
1,
)
def win_item(self) -> None:
self.inventory.add(special_item(), 1)
def special_item() -> str:
return interesting_item() + " of " + random.choice(ITEM_OFS)
def interesting_item() -> str:
return random.choice(ITEM_ATTRIB) + " " + random.choice(SPECIALS)
def boring_item() -> str:
return random.choice(BORING_ITEMS)
class Roster:
def __init__(self) -> None:
self.players: T.List[Player] = []
def add_player(self, player: Player) -> None:
self.players.append(player)
class StatsBuilder:
def __init__(self) -> None:
self.history: T.List[Stats] = []
def roll(self) -> Stats:
stats = Stats()
for stat in PRIME_STATS:
stats[stat] = (
3
+ random.randint(0, 5)
+ random.randint(0, 5)
+ random.randint(0, 5)
)
stats[Stat.hp_max] = random.randint(0, 7) + stats[Stat.condition] // 6
stats[Stat.mp_max] = (
random.randint(0, 7) + stats[Stat.intelligence] // 6
)
self.history.append(stats)
return stats
def unroll(self) -> Stats:
self.history.pop()
return self.history[-1]
def create_player(
name: str, race: Race, class_: Class, stats: Stats
) -> Player:
now = datetime.datetime.now()
random.seed(now)
return Player(
birthday=now, name=name, race=race, class_=class_, stats=stats
)