From b6d7c71d6f08ad5af3c04dbcbcd2933b25dd2eb4 Mon Sep 17 00:00:00 2001 From: cmccabe Date: Fri, 20 Dec 2019 12:17:46 +0000 Subject: [PATCH] added current version of terminal train --- rtc-train.py | 369 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100755 rtc-train.py diff --git a/rtc-train.py b/rtc-train.py new file mode 100755 index 0000000..2f42686 --- /dev/null +++ b/rtc-train.py @@ -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("
")
+  print(train_str)
+  print("
") + quit() + +pad_str = " "*train_len +train.insert(0,pad_str) +train.append(pad_str) + +if __name__ == "__main__": + signal(SIGINT, handler) + curses.wrapper(chuggachugga)