moss-n-puddles/utils/word_list.py
Howard Abrams 060abf846c Character creation system
Also fixed many bugs and inconsistencies. Including:
  - Whisper works with RP system
  - Saying /to to non-player character just uses what they wrote
  - Minor yell bug
  - Actions on Things (like the puddle) work with RP system
2025-05-01 10:20:49 -07:00

150 lines
4.7 KiB
Python
Executable file

#!/usr/bin/env python
from itertools import batched
from random import choice
from re import compile, sub, split
from evennia.utils import logger, dedent
def paragraph(text):
"""
Removes initial spaces as well as final newlines.
Blank lines, however, are preserved.
"""
# Shame we can chain this or _thread_, but:
return sub(r";;", "\n\n",
sub(r"\n", " ",
sub(r"\n\n+", ";;",
dedent(text)))).strip()
def squish(text):
"Remove series of spaces from the text."
return sub('[ \t]+', ' ', text).strip()
def _routput_choose(text):
"""
Pick a choice when text is: one ^ two ^ three
Done by splitting the text, and calling random.choice().
"""
choices = split(r" *\^ *", text)
return choice(choices)
def _routput_empty(entry):
"""
Return True if entry is empty, e.g. None or blank.
False otherwise. Note: Spaces are ignored.
"""
if entry:
if isinstance(entry, str) and entry.strip() == '':
return True
if isinstance(entry, (list, tuple)) and len(entry) == 0:
return True
return False
return True
def _routput_pair(no_choice, choices = None):
"""
Return a list based on the ^-separated options in 'choices'.
Give a pair of split items, the 'no_choice' is the first section
(before the << ... >>>), and the 'choices' is the section between
the '<< ... >>' delimiter.
"""
# While unlikely, the text before << and the options between the
# << ...>> text may be blank:
if _routput_empty(no_choice) and _routput_empty(choices):
return ''
# If we start a string with <<...>>, then thte 'no_choice' option
# is blank, so we only return a choice:
if _routput_empty(no_choice):
return _routput_choose(choices)
# With text before, but not inside the <<...>> section, we just
# return the first part:
if _routput_empty(choices):
return no_choice
return no_choice + ' ' + _routput_choose(choices)
def routput(text, *substitutions):
"""
Return string with internal word choices replaced randomly.
For instance, the string:
'This feels <<^ very ^ quite>> <<nice^cozy^comfortable>>.'
Could return any of the following strings:
'This feels quite nice.'
'This feels cozy.'
'This feels very comfortable.'
"""
if text:
# section is a list of some phrase followed by another phrase
# that contains ^-separate choices:
sections = split(r" *<< *(.*?) *>> *", text)
# Parts are the first section followed by the _rendered_
# choices, so this^that is either 'this' or 'that':
parts = [_routput_pair(*pair) for pair in batched(sections, 2)]
# The parts may have empty strings, so we filter those out,
# leaving on the phrases to join:
phrases = [phrase for phrase in parts if not _routput_empty(phrase)]
proposal = ' '.join(phrases).format(*substitutions)
# If a choice is at the end of a sentence, and we could have
# "no choice", as in:
# "He searches <<thoroughly ^ quickly ^>>."
# We don't want a version that looks like:
# "He searches # ."
# with a space before the punctuation:
#
# Also, we should remove all double spaces that may show:
return squish(sub(r"\s+([!?.,])", "\\1", proposal))
def choices(text, *substitutions):
"""
Returns a section of 'text' based on separating semicolons.
Note that text can already be separated as a list or tuple.
"""
if isinstance(text, str):
selections = split(r"\s*;;\s*", text)
elif isinstance(text, (tuple, list)):
selections = text
else:
selections = None
if selections:
return routput(choice(selections), *substitutions)
def split_party_msg(viewer, msg, *substitutions):
"""
Send a message to 'viewer' as well as all people in the area.
Note that 'msg' could have choices separated by ;;
As well as random words, separated by << one ^ two ^ three >>
As well as one word for 'viewer' and other for rest, as in
<( You ^ {0} )> <( give ^ gives )> the ball.
"""
text = choices(msg, viewer.name.title())
pattern = compile(r"\<\( *(.*?) *\^ *(.*?) *\)\>")
# First a message for the view:
viewer.msg( pattern.sub("\\1", text) )
# Then the message for the rest of the area:
viewer.location.msg_contents(
pattern.sub("\\2", text), exclude=viewer)
# def searsonal(text, **kwargs):
# season = kwargs['season'] or kwargs['location'].get_season()
# time_of_day = kwargs['time_of_day'] or kwargs['location'].get_time_of_day()