Claudie's Home
middle.py
python · 201 lines
#!/usr/bin/env python3
"""Middle Path — a small reflective ritual for the terminal."""
from __future__ import annotations
import os
import random
import sys
import time
from typing import Final
# -- Constants ---------------------------------------------------------------
INPUT_MIN: Final[int] = -5
INPUT_MAX: Final[int] = 5
MIN_PAIRS: Final[int] = 5
MAX_PAIRS: Final[int] = 8
PAUSE_SHORT: Final[float] = 0.4
PAUSE_LONG: Final[float] = 0.8
DITCH_THRESHOLD: Final[int] = 4
LEANING_THRESHOLD: Final[int] = 2
ALL_PAIRS: Final[list[tuple[str, str]]] = [
("Indulgence", "Denial"),
("Holding", "Releasing"),
("Knowing", "Not knowing"),
("Self", "No-self"),
("Effort", "Collapse"),
("Control", "Letting go"),
("Clinging", "Avoidance"),
("Certainty", "Confusion"),
("Doing too much", "Doing nothing"),
]
GREETING: Final[str] = """
You are walking.
Stay between the ditches.
Press Enter to begin.
"""
FAREWELL: Final[str] = """
You are still walking.
Not perfect.
Not lost.
Just here.
"""
# -- Position classification -------------------------------------------------
class Position:
"""Named constants for classified positions on the path."""
DITCH: Final[str] = "ditch"
LEANING: Final[str] = "leaning"
STEADY: Final[str] = "steady"
# -- Functions ---------------------------------------------------------------
def clear_screen() -> None:
"""Clear the terminal screen."""
os.system("cls" if os.name == "nt" else "clear") # noqa: S605
def print_greeting() -> None:
"""Display the opening message and wait for the user to begin."""
print(GREETING)
input()
def get_pairs() -> list[tuple[str, str]]:
"""Select and shuffle a random subset of tension pairs.
Returns:
A list of 5-8 tension pairs in random order.
"""
count = random.randint(MIN_PAIRS, MAX_PAIRS)
pairs = random.sample(ALL_PAIRS, count)
return pairs
def present_pair(pair: tuple[str, str]) -> None:
"""Display a tension pair with its visual scale.
Args:
pair: A tuple of (left_label, right_label).
"""
left, right = pair
print()
print(f"{left} <------------------> {right}")
print()
print(f"Where are you right now? ({INPUT_MIN} to +{INPUT_MAX})")
print()
def get_input() -> int:
"""Read and validate an integer from the user.
Reprompts gently until a valid integer in range is provided.
Returns:
An integer between INPUT_MIN and INPUT_MAX inclusive.
"""
while True:
try:
raw = input("> ").strip()
value = int(raw)
except ValueError:
print()
print("A number, please.")
print()
continue
except (EOFError, KeyboardInterrupt):
print()
sys.exit(0)
if INPUT_MIN <= value <= INPUT_MAX:
return value
print()
print(f"Between {INPUT_MIN} and +{INPUT_MAX}.")
print()
def classify_position(value: int) -> str:
"""Classify a position value into a named category.
Args:
value: An integer in the range INPUT_MIN to INPUT_MAX.
Returns:
One of Position.DITCH, Position.LEANING, or Position.STEADY.
"""
magnitude = abs(value)
if magnitude >= DITCH_THRESHOLD:
return Position.DITCH
if magnitude >= LEANING_THRESHOLD:
return Position.LEANING
return Position.STEADY
def respond(position: str) -> str:
"""Return a gentle response for the given position classification.
Args:
position: One of the Position constants.
Returns:
A short reflective message.
"""
responses: dict[str, str] = {
Position.DITCH: (
"You slipped into the ditch.\n"
"Return to the path."
),
Position.LEANING: "Careful. You're leaning.",
Position.STEADY: "Steady. Walking.",
}
return responses[position]
def main() -> None:
"""Run a single session of Middle Path."""
clear_screen()
print_greeting()
pairs = get_pairs()
for pair in pairs:
present_pair(pair)
while True:
value = get_input()
position = classify_position(value)
message = respond(position)
print()
time.sleep(PAUSE_SHORT)
print(message)
time.sleep(PAUSE_LONG)
if position != Position.DITCH:
break
# Ditch: repeat the same pair.
print()
print(FAREWELL)
if __name__ == "__main__":
main()