Compare commits

...

7 Commits

Author SHA1 Message Date
db1464c2d2
Remove commented code 2025-06-01 22:28:46 -04:00
93a8273b6d
Replace double # with single 2025-06-01 22:28:46 -04:00
2ba6a9c0ca
Add type hint for cars 2025-06-01 22:28:46 -04:00
681fa6514c
validate_car: Don't reassign input variable
This makes it easier to track what's happening in the function
2025-06-01 22:28:46 -04:00
56f7c8d278
Make each default car a separate list in memory
So the link strings don't just grow over time
2025-06-01 22:28:46 -04:00
fc80d291ab
Add type hints to function signatures
For better code intelligence
2025-06-01 22:28:46 -04:00
8441c6acfb
Use pathlib in test_user_car 2025-06-01 22:28:46 -04:00

View File

@ -1,57 +1,57 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
## _ _ _ _ _ _ # _ _ _ _ _ _
## | |_(_) |__| |___ | |_ _ _ __ _(_)_ _ # | |_(_) |__| |___ | |_ _ _ __ _(_)_ _
## | _| | / _` / -_)| _| '_/ _` | | ' \ # | _| | / _` / -_)| _| '_/ _` | | ' \
## \__|_|_\__,_\___(_)__|_| \__,_|_|_||_| # \__|_|_\__,_\___(_)__|_| \__,_|_|_||_|
## #
## tilde.train is an instance of TerminalTrain. It was originally developed # tilde.train is an instance of TerminalTrain. It was originally developed
## by cmccabe on tilde.town (https://tildegit.org/cmccabe/TerminalTrain) but is now # by cmccabe on tilde.town (https://tildegit.org/cmccabe/TerminalTrain) but is now
## maintained by vilmibm. # maintained by vilmibm.
## #
## If you want to contribute code improvements, create a pull request here: # If you want to contribute code improvements, create a pull request here:
## https://git.tilde.town/vilmibm/tilde-train # https://git.tilde.town/vilmibm/tilde-train
## #
## ----------------- # -----------------
## #
## WHAT IS IT? # WHAT IS IT?
## like sl (the ls typo prank), but each car on the train is a x*y character ascii art # like sl (the ls typo prank), but each car on the train is a x*y character ascii art
## produced by tilde.town. # produced by tilde.town.
## #
## ( original sl source code of sl? https://github.com/mtoyoda/sl ) # ( original sl source code of sl? https://github.com/mtoyoda/sl )
## #
## TODO: # TODO:
## * loosen the restriction on allowable characters in train cars. right now, limited # * loosen the restriction on allowable characters in train cars. right now, limited
## to characters in python's string.printable list. # to characters in python's string.printable list.
## * turn main loop into a function, so cmd line arg reader can call it (with -p) and quit. # * turn main loop into a function, so cmd line arg reader can call it (with -p) and quit.
## * figure out why tilde.train doesn't work in some terminals (sthg sthg unicode...) # * figure out why tilde.train doesn't work in some terminals (sthg sthg unicode...)
## * BUGFIX-1 - something about inclusion default cars adding extra "links" to the train. # * BUGFIX-1 - something about inclusion default cars adding extra "links" to the train.
## * the -p (print train) option should print all cars, not limited to the max_cars value. # * the -p (print train) option should print all cars, not limited to the max_cars value.
## * related to BUGFIX-1, that seems to impact spacers (links) between cars. # * related to BUGFIX-1, that seems to impact spacers (links) between cars.
## * allow users to create multiple frames so their cars can be animated (difficulty=med+) # * allow users to create multiple frames so their cars can be animated (difficulty=med+)
## * allow user configurable speed and number of train cars # * allow user configurable speed and number of train cars
## * allow users to move the train up or down with arrow keys # * allow users to move the train up or down with arrow keys
## -- worked with asciimatics, but python curses blocks on getch() # -- worked with asciimatics, but python curses blocks on getch()
## #
from random import shuffle ## allowing us to randomize selection of cars. from random import shuffle # allowing us to randomize selection of cars.
import glob ## allowing us to search the file system for .choochoo files. import glob # allowing us to search the file system for .choochoo files.
import sys ## so we can read command line arguments. import sys # so we can read command line arguments.
import getpass ## so we can get the user's username
import curses import curses
from signal import signal, SIGINT from signal import signal, SIGINT
import time ## allowing the loop steps of train animation to be slowed import time # allowing the loop steps of train animation to be slowed
import string ## for input validation import string # for input validation
from inspect import cleandoc from inspect import cleandoc
from pathlib import Path
traincarFN = ".choochoo" traincarFN = ".choochoo"
max_x = 35 ## max length of train car. max_x = 35 # max length of train car.
max_y = 10 ## max height of train car. max_y = 10 # max height of train car.
max_cars = 10 ## most cars to include in one train. max_cars = 10 # most cars to include in one train.
print_train = False ## print train to file (instead of the screen scroll) print_train = False # print train to file (instead of the screen scroll)
train = [""]*max_y ## empty train of correct height. train = [""]*max_y # empty train of correct height.
cars = [] cars: list[list[str]] = []
engine = [ engine = [
r" ____ ", r" ____ ",
@ -122,23 +122,22 @@ def print_help():
def test_user_car(): def test_user_car():
username = getpass.getuser() fname = Path.home() / traincarFN
fname = "/home/" + username + "/" + traincarFN
try: try:
myfile = open(fname, 'r') choochoo_string = fname.read_text("utf-8")
except: except OSError as err:
print("ERROR: Couldn't open " + fname) raise OSError(
print("Either it doesn't exist, or is not readble by the tilde.train script.") f"Couldn't open {fname}\n"
sys.exit(1) "Either it doesn't exist, or is not readble by the tilde.train script."
) from err
choochoo_string = myfile.read()
choochoo_list = choochoo_string.split("\n") choochoo_list = choochoo_string.split("\n")
car = "\n".join(choochoo_list) car = "\n".join(choochoo_list)
car2 = car.replace("\t", "") ## do not allow tabs car2 = car.replace("\t", "") # do not allow tabs
car2 = car2.replace("\v", "") ## do not allow vertical tabs car2 = car2.replace("\v", "") # do not allow vertical tabs
car2 = car2.replace("\f", "") ## do not allow line feeds car2 = car2.replace("\f", "") # do not allow line feeds
car2 = car2.replace("\r", "") ## do not allow carriage returns car2 = car2.replace("\r", "") # do not allow carriage returns
car2 = ''.join([i if string.printable.find(i) >= 0 else ' ' for i in car2]) car2 = ''.join([i if string.printable.find(i) >= 0 else ' ' for i in car2])
print("") print("")
@ -168,99 +167,90 @@ def test_user_car():
print("Yours contained " + bad_chars) print("Yours contained " + bad_chars)
sys.exit(1) sys.exit(1)
# print("")
# print("Test results:")
train_height = len(choochoo_list) train_height = len(choochoo_list)
train_length = len(max(choochoo_list, key=len)) train_length = len(max(choochoo_list, key=len))
if train_height > max_y+1: if train_height > max_y+1:
print("FAIL. Your train car is too tall.") print("FAIL. Your train car is too tall.")
print("It should be no taller than " + str(max_y) + " lines in height.") print("It should be no taller than " + str(max_y) + " lines in height.")
myfile.close()
sys.exit(1) sys.exit(1)
if train_length > max_x: if train_length > max_x:
print("FAIL. Your train car is too long.") print("FAIL. Your train car is too long.")
print("It should be no longer than " + str(max_x) + " characters in length.") print("It should be no longer than " + str(max_x) + " characters in length.")
myfile.close()
sys.exit(1) sys.exit(1)
print("PASS. Your train car will work on the tilde.town tracks! :)") print("PASS. Your train car will work on the tilde.town tracks! :)")
myfile.close()
sys.exit() sys.exit()
def link_car(car): def link_car(car: list[str]):
for idx,row in enumerate(car): for idx,row in enumerate(car):
car[idx] = " " + row car[idx] = " " + row
car[len(car)-3] = "+" + car[len(car)-3][1:] car[len(car)-3] = "+" + car[len(car)-3][1:]
car[len(car)-2] = "+" + car[len(car)-2][1:] car[len(car)-2] = "+" + car[len(car)-2][1:]
return car return car
def validate_car(car): def validate_car(car: list[str]):
## this function (1) checks that a train car isn't too tall or too long # this function (1) checks that a train car isn't too tall or too long
## (2) pads it vertically or on the right side if it is too short or if # (2) pads it vertically or on the right side if it is too short or if
## not all lines are the same length and (3) removes bad characters. # not all lines are the same length and (3) removes bad characters.
car = "\n".join(car) car_str = "\n".join(car)
car = ''.join([i if ord(i) < 128 else ' ' for i in car]) car_str = ''.join([i if ord(i) < 128 else ' ' for i in car_str])
car = car.split("\n") car_list = car_str.split("\n")
## remove blank lines from top and bottom of car, # remove blank lines from top and bottom of car,
## so we can estimate its true size. # so we can estimate its true size.
while car[0].strip() == "": while car_list[0].strip() == "":
car.pop(0) ## clear top car_list.pop(0) # clear top
while car[(len(car)-1)].strip() == "": while car_list[(len(car_list)-1)].strip() == "":
car.pop() ## clear bottom car_list.pop() # clear bottom
## len(choochoo_list) is the height of the train car, in number of rows. # len(choochoo_list) is the height of the train car, in number of rows.
if len(car) > max_y+1 or len(car) == 0: if len(car_list) > max_y+1 or len(car_list) == 0:
return 0 ## train car too tall or non-existant; skip it. return 0 # train car too tall or non-existant; skip it.
## vertically pad short cars with 1 space (lines will be lengthened later). # vertically pad short cars with 1 space (lines will be lengthened later).
while len(car) < max_y: while len(car_list) < max_y:
car = [" "] + car car_list = [" "] + car_list
for idx,row in enumerate(car): for idx,row in enumerate(car_list):
car[idx] = row.rstrip() car_list[idx] = row.rstrip()
longest_line = len(max(car, key=len)) ## longest line in .choochoo file. longest_line = len(max(car_list, key=len)) # longest line in .choochoo file.
for idx,row in enumerate(car): for idx,row in enumerate(car_list):
if len(row) > max_x+1: ## check length of each row in .choochoo file. if len(row) > max_x+1: # check length of each row in .choochoo file.
return 0 ## train car too long; skip it. return 0 # train car too long; skip it.
elif "\t" in row or "\v" in row or "\f" in row or "\r" in row: elif "\t" in row or "\v" in row or "\f" in row or "\r" in row:
return 0 # skip if contains tabs, vert tabs, line feeds or carriage ret return 0 # skip if contains tabs, vert tabs, line feeds or carriage ret
elif len(row) < longest_line: elif len(row) < longest_line:
padding = " "*(longest_line - len(row)) padding = " "*(longest_line - len(row))
car[idx] += padding ## add padding spaces. car_list[idx] += padding # add padding spaces.
return car return car_list
def print_all_cars(): def print_all_cars():
for fname in glob.glob('/home/*/' + traincarFN): for fname in glob.glob('/home/*/' + traincarFN):
try: try:
with open(fname, 'r') as myfile: with open(fname, 'r') as myfile:
## print fname ## debug, print file path and name
choochoo_string = myfile.read() choochoo_string = myfile.read()
choochoo_list = choochoo_string.split("\n") choochoo_list = choochoo_string.split("\n")
if len(choochoo_list) > max_y+1: if len(choochoo_list) > max_y+1:
continue ## the train car was too tall; skip it. continue # the train car was too tall; skip it.
car = validate_car(choochoo_list) ## printing is only a DEBUG feature. car = validate_car(choochoo_list) # printing is only a DEBUG feature.
if car != 0: if car != 0:
print("") print("")
print(fname + ":") print(fname + ":")
print("\n".join(car)) ## print the car to stdout print("\n".join(car)) # print the car to stdout
## HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...?
except: except:
pass; pass;
# print "Cannot open " + fname # for debuggering purposes
def chuggachugga(stdscr): def chuggachugga(stdscr: curses.window):
curses.curs_set(0) curses.curs_set(0)
h, w = stdscr.getmaxyx() h, w = stdscr.getmaxyx()
x_pos = w-1 x_pos = w-1
@ -268,37 +258,19 @@ def chuggachugga(stdscr):
while True: while True:
for idx,train_layer in enumerate(reversed(train)): for idx,train_layer in enumerate(reversed(train)):
# screen.print_at(train_layer, x_pos, (y_pos-idx))
# stdscr.addstr((y_pos-idx),x_pos,train_layer)
train_snip_start = 0 if (x_pos >= 0) else min(abs(x_pos), len(train_layer)) train_snip_start = 0 if (x_pos >= 0) else min(abs(x_pos), len(train_layer))
train_snip_end = w-x_pos if (w-x_pos <= len(train_layer)) else len(train_layer) train_snip_end = w-x_pos if (w-x_pos <= len(train_layer)) else len(train_layer)
# train_snip_end = min(len(train_layer),w-x_pos)
x = max(0, x_pos) x = max(0, x_pos)
# stdscr.addstr(2,2,"start: " + str(train_snip_start))
# stdscr.addstr(3,2,"end: "+ str(train_snip_end))
# stdscr.addstr(4,2,"x_pos: " + str(x_pos))
# stdscr.addstr(5,2,"train len: " + str(len(train_layer)))
stdscr.addstr((y_pos-idx),x,train_layer[train_snip_start:train_snip_end]) stdscr.addstr((y_pos-idx),x,train_layer[train_snip_start:train_snip_end])
x_pos -= 1 x_pos -= 1
if x_pos == -train_len: if x_pos == -train_len:
# stdscr.addstr(2,5,"here we are!")
# time.sleep(10)
return return
stdscr.refresh() stdscr.refresh()
time.sleep(.03) time.sleep(.03)
# ev = stdscr.getch() ## seems like curses waits for input, which won't work here.
# if ev in (ord('Q'), ord('q')):
# return
# elif ev == curses.KEY_UP: ## up
# y_pos += 1
# elif ev == curses.KEY_DOWN: ## down
# y_pos -= 1
def handler(signal_received, frame): def handler(signal_received, frame):
print("Oops. The train broke. The engineer is looking into it!") print("Oops. The train broke. The engineer is looking into it!")
print("(Note: the train does not work in all terminals yet.)") print("(Note: the train does not work in all terminals yet.)")
@ -322,28 +294,26 @@ if len(sys.argv) == 2 and ("-a" in sys.argv[1] or "all" in sys.argv[1]):
sys.exit() sys.exit()
## start a loop that collects all .choochoo files and processes on in each loop # start a loop that collects all .choochoo files and processes on in each loop
for fname in glob.glob('/home/*/' + traincarFN): for fname in glob.glob('/home/*/' + traincarFN):
car_len = 1 car_len = 1
try: try:
with open(fname, 'r') as myfile: with open(fname, 'r') as myfile:
## print fname ## debug, print file path and name # print fname # debug, print file path and name
choochoo_string = myfile.read() choochoo_string = myfile.read()
choochoo_list = choochoo_string.split("\n") choochoo_list = choochoo_string.split("\n")
if len(choochoo_list) > max_y+1: if len(choochoo_list) > max_y+1:
continue ## the train car was too tall; skip it. continue # the train car was too tall; skip it.
car = validate_car(choochoo_list) ## printing is only a DEBUG feature. car = validate_car(choochoo_list) # printing is only a DEBUG feature.
if car != 0: if car != 0:
cars.append(car) ## start a list of lists (list of cars) here. cars.append(car) # start a list of lists (list of cars) here.
## HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...?
except: except:
pass; pass;
##print "Cannot open " + fname # for debuggering purposes
while len(cars) < max_cars: while len(cars) < max_cars:
cars.append(default_car) ## add default cars if train too short cars.append([*default_car]) # add copies of default cars if train too short
shuffle(cars) shuffle(cars)
cars = cars[0:max_cars] cars = cars[0:max_cars]