Last active
July 12, 2025 06:18
-
-
Save Pinacolada64/7499e783d42371cadfca0ddf719df6bf to your computer and use it in GitHub Desktop.
Text paging from lists and disk files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.