added current version of terminal train
This commit is contained in:
parent
37589f4daf
commit
b6d7c71d6f
|
@ -0,0 +1,369 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
## ___ _____ ___ _____ _
|
||||
## | _ \_ _/ __|_ _| _ __ _(_)_ _
|
||||
## | / | || (__ _| || '_/ _` | | ' \
|
||||
## |_|_\ |_| \___(_)_||_| \__,_|_|_||_|
|
||||
##
|
||||
## TerminalTrain is called RTC.Train here because it is maintained
|
||||
## at rawtext.club. You can, of course, rebrand it to whatever
|
||||
## system you're running it on. Or, feel free to sign up for a free
|
||||
## rawtext.club account and help us build this and other tools!
|
||||
##
|
||||
## created originall by cmccabe@tilde.town - spring 2018-ish.
|
||||
## ported from asciimatics to python curses late 2019.
|
||||
##
|
||||
## WHAT?
|
||||
## like sl (the ls typo prank), but each car on the train is a x*y character ascii art
|
||||
## produced by rawtext.club members.
|
||||
##
|
||||
## ( original sl source code of sl? https://github.com/mtoyoda/sl )
|
||||
##
|
||||
## TODO:
|
||||
## * loosen the restriction on allowable characters in train cars. right now, limited
|
||||
## 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.
|
||||
## * figure out why rtc.train doesn't work in some terminals (sthg sthg unicode...)
|
||||
## * 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.
|
||||
## * 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 user configurable speed and number of train cars
|
||||
## * allow users to move the train up or down with arrow keys
|
||||
## -- worked with asciimatics, but python curses blocks on getch()
|
||||
##
|
||||
|
||||
from random import shuffle ## allowing us to randomize selection of cars.
|
||||
import glob ## allowing us to search the file system for .choochoo files.
|
||||
import sys ## so we can read command line arguments.
|
||||
import getpass ## so we can get the user's username
|
||||
import curses
|
||||
from signal import signal, SIGINT
|
||||
import time ## allowing the loop steps of train animation to be slowed
|
||||
import string ## for input validation
|
||||
|
||||
traincarFN = ".choochoo"
|
||||
max_x = 35 ## max length of train car.
|
||||
max_y = 10 ## max height of train car.
|
||||
max_cars = 2 ## most cars to include in one train.
|
||||
print_train = False ## print train to file (instead of the screen scroll)
|
||||
|
||||
train = [""]*max_y ## empty train of correct height.
|
||||
cars = []
|
||||
|
||||
engine = """ ____
|
||||
|____| ------------
|
||||
| | === | ------ |
|
||||
___| |__| |_____| | O | |
|
||||
| | | |__/V\_| |
|
||||
[[ | |
|
||||
| | ------------ | rtc |
|
||||
|__|______________|__________|
|
||||
//// / _\__/__\__/__\ / \
|
||||
//// \__/ \__/ \__/ \__/ """
|
||||
engine = engine.split("\n")
|
||||
|
||||
caboose = """ ||
|
||||
============= ||
|
||||
=========| |==========
|
||||
| ---- ---- |
|
||||
| | | | | |
|
||||
| ---- ---- |
|
||||
| rtc railways |
|
||||
==| |==
|
||||
== - / \-/ \-----/ \-/ \ - ==
|
||||
\__/ \__/ \__/ \__/ """
|
||||
caboose = caboose.split("\n")
|
||||
|
||||
default_car = """ ----------------------------
|
||||
| |
|
||||
| YOUR TRAIN CAR HERE! |
|
||||
| Just create a |
|
||||
| ~/.choochoo file! |
|
||||
| __ __ __ __ |
|
||||
- / \-/ \------/ \-/ \ -
|
||||
\__/ \__/ \__/ \__/"""
|
||||
default_car = default_car.split("\n")
|
||||
|
||||
|
||||
def print_help():
|
||||
print("")
|
||||
print("~ ~ Hooray! You've found the rtc.train! ~ ~")
|
||||
print("")
|
||||
print("To add your own car to a future train, create")
|
||||
print("a .choochoo file in your home directory and")
|
||||
print("make sure it is 'other' readable, for example:")
|
||||
print("")
|
||||
print(" chmod 644 ~/.choochoo")
|
||||
print("")
|
||||
print("The file should contain an ascii drawing of a")
|
||||
print("train car no more than " + str(max_x) + " characters wide")
|
||||
print("and " + str(max_y) + " characters tall.")
|
||||
print("")
|
||||
print("Only printable ascii characters are accepted for now.")
|
||||
print("Run the command again followed by a -t switch to test")
|
||||
print("your .choochoo file and report any non accepted chars.")
|
||||
print("")
|
||||
print("Each train contains a random selection of cars")
|
||||
print("from across rawtext.club user home directories.")
|
||||
print("Don't worry, yours will be coming around the")
|
||||
print("bend soon!")
|
||||
print("")
|
||||
print("~ ~ ~ ~ ~ ~")
|
||||
print("")
|
||||
|
||||
|
||||
def test_user_car():
|
||||
username = getpass.getuser()
|
||||
fname = "/home/" + username + "/" + traincarFN
|
||||
try:
|
||||
myfile = open(fname, 'r')
|
||||
except:
|
||||
print("ERROR: Couldn't open " + fname)
|
||||
print("Either it doesn't exist, or is not readble by the rtc.train script.")
|
||||
exit()
|
||||
|
||||
choochoo_string = myfile.read()
|
||||
choochoo_list = choochoo_string.split("\n")
|
||||
|
||||
car = "\n".join(choochoo_list)
|
||||
car2 = car.replace("\t", "") ## do not allow tabs
|
||||
car2 = car2.replace("\v", "") ## do not allow vertical tabs
|
||||
car2 = car2.replace("\f", "") ## do not allow line feeds
|
||||
car2 = car2.replace("\r", "") ## do not allow carriage returns
|
||||
car2 = ''.join([i if string.printable.find(i) >= 0 else ' ' for i in car2])
|
||||
|
||||
print("")
|
||||
print("Test results:")
|
||||
|
||||
if car != car2:
|
||||
print("")
|
||||
print("Your train car contains an invalid character. Sorry, ")
|
||||
print("for now only standard ascii characters are allowed.")
|
||||
print("You can still use most other characters (except tabs),")
|
||||
print("but they will be replaced by a standard ascii char when")
|
||||
print("the train is built.")
|
||||
|
||||
bad_chars = []
|
||||
for i in enumerate(car):
|
||||
if string.printable.find(i[1]) < 0:
|
||||
bad_chars.append(i[1])
|
||||
if i[1] in ("\t", "\v", "\f", "\r"):
|
||||
bad_chars.append("other whitespace")
|
||||
bad_chars = set(bad_chars)
|
||||
bad_chars = ", ".join(bad_chars)
|
||||
|
||||
print("")
|
||||
print("The following currently the only accepted characters: ")
|
||||
print(string.printable.strip())
|
||||
print("")
|
||||
print("Yours contained " + bad_chars)
|
||||
exit()
|
||||
|
||||
# print("")
|
||||
# print("Test results:")
|
||||
|
||||
train_height = len(choochoo_list)
|
||||
train_length = len(max(choochoo_list, key=len))
|
||||
|
||||
if train_height > max_y+1:
|
||||
print("FAIL. Your train car is too tall.")
|
||||
print("It should be no taller than " + str(max_y) + " lines in height.")
|
||||
myfile.close()
|
||||
exit()
|
||||
|
||||
if train_length > max_x:
|
||||
print("FAIL. Your train car is too long.")
|
||||
print("It should be no longer than " + str(max_x) + " characters in length.")
|
||||
myfile.close()
|
||||
exit()
|
||||
|
||||
print("PASS. Your train car will work on the rtc.tracks! :)")
|
||||
myfile.close()
|
||||
exit()
|
||||
|
||||
def link_car(car):
|
||||
for idx,row in enumerate(car):
|
||||
car[idx] = " " + row
|
||||
car[len(car)-3] = "+" + car[len(car)-3][1:]
|
||||
car[len(car)-2] = "+" + car[len(car)-2][1:]
|
||||
return car
|
||||
|
||||
def validate_car(car):
|
||||
## 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
|
||||
## not all lines are the same length and (3) removes bad characters.
|
||||
|
||||
car = "\n".join(car)
|
||||
car = ''.join([i if ord(i) < 128 else ' ' for i in car])
|
||||
car = car.split("\n")
|
||||
|
||||
## remove blank lines from top and bottom of car,
|
||||
## so we can estimate its true size.
|
||||
while car[0].strip() == "":
|
||||
car.pop(0) ## clear top
|
||||
while car[(len(car)-1)].strip() == "":
|
||||
car.pop() ## clear bottom
|
||||
|
||||
## len(choochoo_list) is the height of the train car, in number of rows.
|
||||
if len(car) > max_y+1 or len(car) == 0:
|
||||
return 0 ## train car too tall or non-existant; skip it.
|
||||
|
||||
## vertically pad short cars with 1 space (lines will be lengthened later).
|
||||
while len(car) < max_y:
|
||||
car = [" "] + car
|
||||
|
||||
for idx,row in enumerate(car):
|
||||
car[idx] = row.rstrip()
|
||||
|
||||
longest_line = len(max(car, key=len)) ## longest line in .choochoo file.
|
||||
|
||||
for idx,row in enumerate(car):
|
||||
if len(row) > max_x+1: ## check length of each row in .choochoo file.
|
||||
return 0 ## train car too long; skip it.
|
||||
elif len(row) < longest_line:
|
||||
padding = " "*(longest_line - len(row))
|
||||
car[idx] += padding ## add padding spaces.
|
||||
|
||||
return car
|
||||
|
||||
|
||||
def print_all_cars():
|
||||
for fname in glob.glob('/home/*/' + traincarFN):
|
||||
try:
|
||||
with open(fname, 'r') as myfile:
|
||||
## print fname ## debug, print file path and name
|
||||
choochoo_string = myfile.read()
|
||||
choochoo_list = choochoo_string.split("\n")
|
||||
if len(choochoo_list) > max_y+1:
|
||||
continue ## the train car was too tall; skip it.
|
||||
|
||||
car = validate_car(choochoo_list) ## printing is only a DEBUG feature.
|
||||
if car != 0:
|
||||
print("")
|
||||
print(fname + ":")
|
||||
print("\n".join(car)) ## print the car to stdout
|
||||
|
||||
## HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...?
|
||||
except:
|
||||
pass;
|
||||
# print "Cannot open " + fname # for debuggering purposes
|
||||
|
||||
|
||||
def chuggachugga(stdscr):
|
||||
curses.curs_set(0)
|
||||
h, w = stdscr.getmaxyx()
|
||||
x_pos = w-1
|
||||
y_pos = int(round(h/2))
|
||||
|
||||
while True:
|
||||
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_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)
|
||||
# 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])
|
||||
|
||||
x_pos -= 1
|
||||
if x_pos == -train_len:
|
||||
# stdscr.addstr(2,5,"here we are!")
|
||||
# time.sleep(10)
|
||||
return
|
||||
|
||||
stdscr.refresh()
|
||||
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):
|
||||
print("Oops. The train broke. The engineer is looking into it!")
|
||||
print("(Note: the train does not work in all terminals yet.)")
|
||||
exit(0)
|
||||
|
||||
default_car = validate_car(default_car)
|
||||
|
||||
if len(sys.argv) == 2 and ("-h" in sys.argv[1] or "help" in sys.argv[1]):
|
||||
print_help()
|
||||
quit()
|
||||
|
||||
if len(sys.argv) == 2 and ("-t" in sys.argv[1] or "test" in sys.argv[1]):
|
||||
test_user_car()
|
||||
quit()
|
||||
|
||||
if len(sys.argv) == 2 and ("-p" in sys.argv[1] or "print" in sys.argv[1]):
|
||||
print_train = True
|
||||
|
||||
if len(sys.argv) == 2 and ("-a" in sys.argv[1] or "all" in sys.argv[1]):
|
||||
print_all_cars()
|
||||
quit()
|
||||
|
||||
|
||||
## start a loop that collects all .choochoo files and processes on in each loop
|
||||
for fname in glob.glob('/home/*/' + traincarFN):
|
||||
car_len = 1
|
||||
try:
|
||||
with open(fname, 'r') as myfile:
|
||||
## print fname ## debug, print file path and name
|
||||
choochoo_string = myfile.read()
|
||||
choochoo_list = choochoo_string.split("\n")
|
||||
if len(choochoo_list) > max_y+1:
|
||||
continue ## the train car was too tall; skip it.
|
||||
|
||||
car = validate_car(choochoo_list) ## printing is only a DEBUG feature.
|
||||
if car != 0:
|
||||
cars.append(car) ## start a list of lists (list of cars) here.
|
||||
|
||||
## HOW TO CLOSE THE FILE HANDLE? fname.close(), close(fname), ...?
|
||||
except:
|
||||
pass;
|
||||
##print "Cannot open " + fname # for debuggering purposes
|
||||
|
||||
while len(cars) < max_cars:
|
||||
cars.append(default_car) ## add default cars if train too short
|
||||
|
||||
shuffle(cars)
|
||||
cars = cars[0:max_cars]
|
||||
|
||||
for idx,car in enumerate(cars):
|
||||
cars[idx] = link_car(cars[idx])
|
||||
|
||||
cars.insert(0, engine)
|
||||
caboose = link_car(caboose)
|
||||
cars.append(caboose)
|
||||
|
||||
for i in cars:
|
||||
n = 0
|
||||
for j in i:
|
||||
train[n] += j
|
||||
n+=1
|
||||
|
||||
train_len = len(str(train[0]))
|
||||
train_str = "\n".join(train)
|
||||
|
||||
if print_train:
|
||||
print("<pre>")
|
||||
print(train_str)
|
||||
print("</pre>")
|
||||
quit()
|
||||
|
||||
pad_str = " "*train_len
|
||||
train.insert(0,pad_str)
|
||||
train.append(pad_str)
|
||||
|
||||
if __name__ == "__main__":
|
||||
signal(SIGINT, handler)
|
||||
curses.wrapper(chuggachugga)
|
Loading…
Reference in New Issue