Skip to content

Instantly share code, notes, and snippets.

@Pinacolada64
Last active July 12, 2025 06:18
Show Gist options
  • Save Pinacolada64/7499e783d42371cadfca0ddf719df6bf to your computer and use it in GitHub Desktop.
Save Pinacolada64/7499e783d42371cadfca0ddf719df6bf to your computer and use it in GitHub Desktop.
Text paging from lists and disk files
# from new_player_2 import Player
import logging
from colorama import Fore
import re
# from flags import PlayerFlags
# from tada_utilities import a_or_an
import textwrap
# from tada_utilities import a_or_an
def a_or_an(string: str) -> str:
"""Return 'a' or 'an' ' <string>' based on the first letter of the string being a vowel or not"""
starts_with_vowel = string.lower().startswith(("a", "e", "i", "o", "u"))
article = "an" if starts_with_vowel else "a"
return f"{article} {string}"
# Assume a simple Player class for demonstration purposes if new_player_2 isn't fully defined
# In a real scenario, this would come from your new_player_2 import.
class Player:
def __init__(self, screen_height=24, screen_columns=80):
self.client_settings = type('ClientSettings', (object,), {
'screen_height': screen_height,
'screen_columns': screen_columns
})()
self.flags = set()
def query_flag(self, flag):
return flag in self.flags
def output(self, message):
print(message)
# Assuming PlayerFlags and Guild enums are defined elsewhere, as in your original code.
# For testing purposes, I'll define simple mock versions.
class PlayerFlags:
MORE_PROMPT = "MORE_PROMPT"
class Message:
def __init__(self, lines=None, error_line=None):
self.lines = lines if lines is not None else []
self.error_line = error_line
if error_line:
logging.error(error_line)
if lines:
for line in lines:
print(line)
class Guild:
CIVILIAN = "Civilian"
OUTLAW = "Outlaw"
# Add other guilds if necessary for your main block's full execution
IRON_FIST = "Iron Fist"
MARK_OF_THE_CLAW = "Mark of the Claw"
MARK_OF_THE_SWORD = "Mark of the Sword"
class GameInterface:
def __init__(self):
pass
def bulleted_list_format(text: str, width: int, initial_indent: str = "* ", subsequent_indent: str = ' ' * 2) -> str:
"""
Formats a single paragraph into a bulleted list string, handling wrapping.
This function is primarily for *creating* bulleted content from raw text.
It returns a single string that may contain newlines if the text wrapped.
"""
logging.info(f"bulleted_list_format input text: '{text}', width: {width}")
# Dedent only if needed, but for direct bullet content, strip is often more reliable
dedented_text = textwrap.dedent(text).strip()
# Fill the text with the specified indents. This will handle the wrapping.
formatted_text = textwrap.fill(dedented_text, width=width,
initial_indent=initial_indent, subsequent_indent=subsequent_indent)
logging.info(f"bulleted_list_format output: '{formatted_text}'")
return formatted_text
def text_pager(text_lines: list, p: Player):
"""
Display a list of strings in a paged fashion, accounting for Player's screen height
and wrapping text using textwrap. Empty list elements are considered newlines.
:param text_lines: list of strings to display. Each string is treated as a paragraph
that needs to be wrapped.
:param p: Player object holding screen_height and screen_columns values.
:return: None
"""
screen_height = p.client_settings.screen_height
screen_width = p.client_settings.screen_columns
wrapped_content = []
for line_raw in text_lines: # Renamed 'line' to 'line_raw' for clarity
if not line_raw.strip(): # Check if line is empty or just whitespace
wrapped_content.append("") # Treat empty/blank lines as explicit newlines
continue # Skip to next line_raw in input
# Apply highlighting before wrapping to avoid breaking color codes
highlighted_line_content = re.sub(r'\[(.+?)]', f'{Fore.RED}' + r'\1' + f'{Fore.RESET}', string=line_raw)
# Now, handle wrapping based on content type
if highlighted_line_content.strip().startswith("* "):
# This is a bullet point. We need to handle its indentation specifically.
# Remove the initial '* ' for wrapping, then re-add it with initial_indent.
content_after_bullet = highlighted_line_content.strip()[
2:].strip() # Remove "* " and any leading/trailing space
# Use textwrap.wrap for line-by-line control
# The 'initial_indent' applies only to the first line of the wrapped output
# The 'subsequent_indent' applies to all following wrapped lines
wrapped_bullet_lines = textwrap.wrap(content_after_bullet, width=screen_width,
initial_indent="* ", subsequent_indent=' ' * 2)
wrapped_content.extend(wrapped_bullet_lines)
else:
# Regular paragraph, just wrap it
wrapped_paragraph_lines = textwrap.wrap(highlighted_line_content, width=screen_width)
wrapped_content.extend(wrapped_paragraph_lines)
total_display_lines = len(wrapped_content)
current_line_index = 0
# Calculate total pages upfront
lines_per_page = screen_height - 1 # One line for the prompt
total_pages = (total_display_lines + lines_per_page - 1) // lines_per_page
if total_pages == 0 and total_display_lines > 0:
total_pages = 1
elif total_pages == 0 and total_display_lines == 0: # Handle completely empty content
total_pages = 1 # Or 0 depending on desired behavior for empty file. 1 allows a prompt to show.
while True:
# Calculate current page number
current_page = (current_line_index // lines_per_page) + 1
# Clear screen (optional, depending on your terminal/game environment)
# print("\033c", end="") # ANSI escape code to clear screen
lines_to_display = wrapped_content[current_line_index: current_line_index + lines_per_page]
for display_line in lines_to_display: # Renamed 'line' to 'display_line' for clarity
print(display_line)
remaining_lines = total_display_lines - (current_line_index + len(lines_to_display))
# Pager prompt
# Adjust prompt based on whether there's more content or if back is possible
prompt_options = []
if remaining_lines > 0:
prompt_options.append("[Enter]: Continue")
if current_line_index > 0: # Only offer 'B' if not on the first page
prompt_options.append("[-, B]: Back")
prompt_options.append("[Q]: Quit")
# Dynamically build the prompt message including page numbers
page_info = f"({current_page}/{total_pages})" if total_pages > 1 else ""
status_message = f"-- More {page_info} --" if remaining_lines > 0 else f"-- End {page_info} --"
prompt_message = f"{status_message} {', '.join(prompt_options)}: "
user_input = input(prompt_message).strip().lower()
print() # Add a newline after user input for better readability
if user_input == 'q':
print("(You quit reading.)")
break
elif user_input == '' and remaining_lines > 0:
current_line_index += lines_per_page
# Don't "snap back" immediately here, let the next loop iteration handle end-of-content check naturally.
# If current_line_index goes beyond total_display_lines, remaining_lines will be <= 0 in next iter.
elif user_input == '' and remaining_lines <= 0: # Use <= 0 to catch 0 and negative (overflow)
break # Quit if Enter at end of content
elif user_input in ['b', '-']:
current_line_index = max(0, current_line_index - lines_per_page)
else:
print("Invalid input. Please use 'Enter' to continue, 'B' to go back, or 'Q' to quit.")
def fileread(self, filename: str, p: Player):
"""
display a file to a user in 40 or 80 columns with more_prompt paging
also handles highlighting [text in brackets] via re and colorama
"""
# Message class is defined at the top now, no need to import here
logging.info(f"Reading {filename=}")
file_content_lines = []
cols = p.client_settings.screen_columns
file_handle_path = f"{filename}-{cols}.txt"
logging.info(f"Reading '{file_handle_path}'")
try:
with open(f'{file_handle_path}', newline='\n') as file:
for line in file:
clean_line = line.rstrip('\n')
# comment, just like Python:
if not clean_line.startswith('#'):
file_content_lines.append(clean_line)
text_pager(file_content_lines, p)
except FileNotFoundError:
return Message(lines=[], error_line=f'File {file_handle_path} not found.')
except Exception as e:
logging.error(f"Error reading file {file_handle_path}: {e}")
return Message(lines=[], error_line=f'An error occurred while reading file {file_handle_path}.')
return Message(lines=["(Finished reading.)"])
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
p = Player(screen_height=25, screen_columns=80) # Set a screen height to easily test paging
# Using an apostrophe variable for consistency, though triple-quotes often solve this directly
# for multiline f-strings if they don't contain conflicting quotes.
apostrophe = "'"
print("\n--- Testing text_pager directly ---")
text = [
'"Would you like to join a Guild?" asks Verus expectantly, leaning towards you.',
'',
"No, stay a [C]ivilian. This option is generally recommended for new players who want to get a feel for the game without the added complexities of guild politics and dueling. It provides a safer environment to learn the ropes.",
'',
"* You are safe from dueling by all but Outlaws. This is a very important rule for civilians to remember.",
"* You may only duel Outlaws. Your interactions with other civilians will not involve duels.",
"* You may remain in the Shoppe while you sleep. This provides a safe haven for rest.",
'',
"No, become an [O]utlaw (not recommended for first-time players!)",
"",
"For an Outlaw, the very notion of a Guild is an insult. These are characters who thrive on defiance, going against the grain and making enemies of most others in the Land rather than bending to any collective will or enjoying the camaraderie of a group to be part of.",
'',
f"Becoming an Outlaw drastically changes your gameplay experience, making you a target for many players but also opening up unique opportunities for defiance and solo play.",
'',
"Yes, join a [G]uild",
'',
"Joining a Guild introduces several new facets to life in the Land:",
"",
"* You may battle opposing Guild members with the [DUEL] command. If you are victorious against the opposing guild, the room you are in changes to your guild's alignment. Future duels there award a guild combat bonus against opposing guilds. This is a major advantage for controlling strategic locations.",
"",
"* The [AUTODUEL] command automatically [DUEL]s opposing guild members, even while you are asleep ([fact check this]). This feature offers continuous engagement for dedicated guild members.",
"",
f"* You gain access to your Guild headquarters. This serves as a vital refuge, a place where you can both contribute to and benefit from your fellow members. Whether you{apostrophe}re donating items or silver to aid others, or taking what you need from shared resources, it{apostrophe}s a hub of mutual support. You can also leave messages or reply to others on a public bulletin board.",
'',
]
civilian_info = ['Civilian: The Path of Peace',
'',
'Do you prefer a quieter existence, free from the entanglements of guild wars? As a Civilian, you walk a path of peace and prosperity.',
'',
"You are safe from dueling by all but Outlaws and may only duel Outlaws. You may remain in the Shoppe while you sleep, offering a secure refuge. This choice is ideal for those who wish to focus on trade, crafting, or simply exploring the Land's lore without the constant call to arms."
]
claw_info = ["Mark of the Claw: Embrace the Wild Within",
"",
"For the soul intertwined with nature, for the mystic who commands the untamed forces of the world, the Mark of the Claw calls.",
"",
"If you feel the whisper of the ancient forests, the roar of the wild beasts, and the surge of primal magic, then the Claw is your destiny. This guild is a sanctuary for those who draw power from the earth itself – Druids, Rangers, and mystical scholars. We are guardians of the natural balance, fiercely protective of the wildlands, and masters of forms both fey and fearsome. Join us, and let the untamed power of nature flow through you as you defend the Land with claw and spell!",
]
fist_info = ["The Iron Fist: Dominate and Conquer",
""
"For those who seek undeniable power, for leaders who forge destiny through sheer will, the Iron Fist extends its grip.",
"",
"If your ambition knows no bounds, if you yearn to command and to dominate, then the Iron Fist offers the path to true supremacy. We are the architects of empire, a guild for tacticians, warlords, and those who bend others to their will. We believe in strength, strategy, and the right of the powerful to rule. Join the Iron Fist, and together, we shall reshape the Land under our unyielding command!"]
sword_info = ["Mark of the Sword: Forge Your Legend in Steel",
""
"For the unyielding spirit, for the warrior whose heart beats with the rhythm of battle, the Mark of the Sword awaits.",
""
"If the clash of steel is music to your ears, if disciplined combat and unwavering bravery are your hallmarks, then the Sword is your true home. This guild is the bastion of strength and honor, a brotherhood of Fighters, Knights, and disciplined combatants who stand as the Land's shield. We train relentlessly, fight with courage, and our collective might is an unbreakable force against all who threaten peace. Draw your blade with us, and carve your saga into the annals of history!"
]
outlaw_info = ['Outlaw: The Path of Defiance',
'For the lone wolf, for the rebel who bows to no one, the Outlaw life beckons – but be warned, it is not for the faint of heart!',
'As an Outlaw, the very notion of a Guild is an insult. You thrive on defiance, going against the grain and making enemies of most others in the Land rather than bending to any collective will or enjoying the camaraderie of a group. This path drastically changes your gameplay, making you a target but opening unique opportunities for defiance and solo glory.']
text_pager(text, p)
test_file = "test_file"
with open(f"{test_file}-{p.client_settings.screen_columns}.txt", "w") as f:
f.write("# This is a comment and should be skipped\n")
f.write("Line 1 of the test file.\n")
f.write("Line 2, a [highlighted] word.\n")
f.write("\n")
f.write(
"Line 4, a very long line that should definitely wrap around the screen if the column width is set correctly. This demonstrates the word-wrapping functionality effectively for text files.\n")
f.write("* This is a bullet point from the file.\n")
f.write(" This is a continuation of the bullet point that should be correctly indented.\n")
f.write("* Another [bullet] point from the file. This one is also quite long to test wrapping.\n")
f.write(
"This is a regular paragraph following the bullet points. It should not have any special indentation.\n")
for line_number in range(5, 51): # end range is always the ending number + 1
f.write(f"Line {line_number}.\n")
f.write("Line 51: End of file.\n")
print("\n--- Testing fileread ---")
game_interface = GameInterface()
result = fileread(game_interface, test_file, p)
print(f"fileread result: {result.lines}")
print("\n--- Guild Selection (original block concept) ---")
while True:
menu = ["Help menu mockup:",
'',
'Join Info',
" [C IC] Civilians",
" [O IO] Outlaws",
" [F IF] Iron Fist guild",
" [M IM] Mark of the Claw guild",
" [S IS] Mark of the Sword guild",
]
text_pager(menu, p)
guild_choice = input("Which option [C/IC, O/IO, F/IF, M/IM, S/IS]: ").lower()
print()
if guild_choice in ['ic']:
text_pager(civilian_info, p)
if guild_choice in ['io']:
text_pager(outlaw_info, p)
if guild_choice in ['if']:
text_pager(fist_info, p)
if guild_choice in ['im']:
text_pager(claw_info, p)
if guild_choice in ['is']:
text_pager(sword_info, p)
if guild_choice == 'm':
p.guild = Guild.MARK_OF_THE_CLAW
# p.output(f'"You have chosen to join {p.guild}."')
break
elif guild_choice == 's':
p.guild = Guild.MARK_OF_THE_SWORD
# p.output(f'"You have chosen to join {p.guild}."')
break
elif guild_choice == 'f':
p.guild = Guild.IRON_FIST
# p.output(f'"You have chosen to join {p.guild}."')
break
elif guild_choice == 'c':
p.guild = Guild.CIVILIAN
# p.output(f'"You have chosen to be a {p.guild}."')
break
elif guild_choice == 'o':
p.guild = Guild.OUTLAW
# p.output(f'"You have chosen to be an {p.guild}."')
break
else:
print("Invalid choice.")
choice = ''
if guild_choice in ['m', 's', 'f']:
choice = f"You have chosen to join the {p.guild} guild."
elif guild_choice in ['o', 'c']:
choice = f"You have chosen to be {a_or_an(p.guild)}."
print(choice)
@Pinacolada64
Copy link
Author

Pinacolada64 commented Jul 12, 2025

Guild text:

Choosing Your Path: A Guide to the Guilds

Welcome, adventurer, to the Land! As you embark on your journey, one of the most pivotal choices you'll make is whether to join a Guild. Your guild will shape your destiny, influence your battles, and connect you with brothers and sisters dedicated to a shared vision. Consider your inner calling, for the path you choose will define your legend.

The Guilds of the Land

Mark of the Claw: Embrace the Wild Within

For the soul intertwined with nature, for the mystic who commands the untamed forces of the world, the Mark of the Claw calls.

If you feel the whisper of the ancient forests, the roar of the wild beasts, and the surge of primal magic, then the Claw is your destiny. This guild is a sanctuary for those who draw power from the earth itself – Druids, Rangers, and mystical scholars. We are guardians of the natural balance, fiercely protective of the wildlands, and masters of forms both fey and fearsome. Join us, and let the untamed power of nature flow through you as you defend the Land with claw and spell!

Mark of the Sword: Forge Your Legend in Steel

For the unyielding spirit, for the warrior whose heart beats with the rhythm of battle, the Mark of the Sword awaits.

If the clash of steel is music to your ears, if disciplined combat and unwavering bravery are your hallmarks, then the Sword is your true home. This guild is the bastion of strength and honor, a brotherhood of Fighters, Knights, and disciplined combatants who stand as the Land's shield. We train relentlessly, fight with courage, and our collective might is an unbreakable force against all who threaten peace. Draw your blade with us, and carve your saga into the annals of history!

The Iron Fist: Dominate and Conquer

For those who seek undeniable power, for leaders who forge destiny through sheer will, the Iron Fist extends its grip.

If your ambition knows no bounds, if you yearn to command and to dominate, then the Iron Fist offers the path to true supremacy. We are the architects of empire, a guild for tacticians, warlords, and those who bend others to their will. We believe in strength, strategy, and the right of the powerful to rule. Join the Iron Fist, and together, we shall reshape the Land under our unyielding command!

Or choose an independent path:

Civilian: The Path of Peace

Prefer a quieter existence, free from the entanglements of guild wars? As a Civilian, you walk a path of peace and prosperity.

You are safe from dueling by all but Outlaws and may only duel Outlaws. You may remain in the Shoppe while you sleep, offering a secure refuge. This choice is ideal for those who wish to focus on trade, crafting, or simply exploring the Land's lore without the constant call to arms.

Outlaw: The Path of Defiance

For the lone wolf, for the rebel who bows to no one, the Outlaw life beckons – but be warned, it is not for the faint of heart!

As an Outlaw, the very notion of a Guild is an insult. You thrive on defiance, going against the grain and making enemies of most others in the Land rather than bending to any collective will or enjoying the camaraderie of a group. This path drastically changes your gameplay, making you a target but opening unique opportunities for defiance and solo glory.

Which path truly resonates with your spirit, adventurer? Your choice will shape the battles you fight, the allies you gain, and the legacy you leave behind.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment