diff --git a/.development/tilda_tools.py b/.development/tilda_tools.py index 15f1fa9..44424f3 100755 --- a/.development/tilda_tools.py +++ b/.development/tilda_tools.py @@ -61,6 +61,7 @@ def main(): cmd_parser.add_argument('-b', '--baudrate', default=115200, help='the baud rate of the serial device') cmd_parser.add_argument('-v', '--verbose', action='store_true', help='adds more output') cmd_parser.add_argument('--skip-wifi', action='store_true', help='does not sync wifi.json') + cmd_parser.add_argument('--bootstrapped-apps', action='store_true', help='[Sync] only bootstrapped apps by default') cmd_parser.add_argument('--print_resources', action='store_true', help='prints resources in json') cmd_parser.add_argument('--boot', help='defines which app to boot into after reboot') cmd_parser.add_argument('--run', help='like run, but after a sync') @@ -120,9 +121,17 @@ def main(): pyboard_util.hard_reset(args) if command == "sync": + paths = args.paths if len(args.paths) else None + if args.bootstrapped_apps: + for k,val in list(resources.items()): + if val.get("type", None) == "app": + if not k in paths and not val.get("bootstrapped", False): + if args.verbose: + print("Removing app '{0}' from sync list".format(k)) + del resources[k] + if args.clean: sync.clean(args) - paths = args.paths if len(args.paths) else None synced_resources = sync.sync(args, paths, resources, args.verbose, args.skip_wifi) if (command in ["reset", "sync"]) or run_tests: diff --git a/.gitignore b/.gitignore index 7613256..7505e99 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ __pycache__ wifi*.json config.json +cmd.exe.lnk +tilda_tools.bat \ No newline at end of file diff --git a/3dspin/main.py b/3dspin/main.py index 4cff9f8..c444b4a 100644 --- a/3dspin/main.py +++ b/3dspin/main.py @@ -1,9 +1,9 @@ """3d rotating polyhedra. 2016 badge competition winner, ported for 2018!""" -___name___ = "3D Spin" +___title___ = "3D Spin" ___license___ = "MIT" ___categories___ = ["Demo"] -___dependencies___ = ["app", "ugfx_helper", "random", "sleep", "buttons"] +___dependencies___ = ["app", "ugfx_helper", "sleep", "buttons"] import ugfx from tilda import Buttons diff --git a/DevRant/main.py b/DevRant/main.py new file mode 100644 index 0000000..00920aa --- /dev/null +++ b/DevRant/main.py @@ -0,0 +1,82 @@ +"""DevRant Client for TiLDA-MK4 +""" +___name___ = "DevRant" +___license___ = "MIT" +___dependencies___ = ["app", "wifi", "http", "ugfx_helper"] +___categories___ = ["Other"] +___launchable___ = True + +import ugfx, wifi, http, json, utime, ugfx_helper, dialogs, app + +char_ln = 25 +ln_pg = 19 + +def loop(): + skip = 0 + while True: + ugfx.clear(ugfx.html_color(0x544c6d)) + data= json.loads(http.get("https://devrant.com/api/devrant/rants?app=3&sort=top&range=day&limit=1&skip="+str(skip)).raise_for_status().content)["rants"][0] + + text=data["text"].split(" ") + screens = [[]] + line = "" + screen = 0 + for word in text: + if len(line+word)+1 >= char_ln: + if len(screens[screen]) >= ln_pg: + screen+=1 + screens.append([]) + screens[screen].append(line) + line=word + else: + line = line + " " + word + if len(screens[screen]) < ln_pg: + screens[screen].append(line) + else: + screens.append([line]) + + + hold=True + page = 0 + while hold: + ugfx.clear(ugfx.html_color(0x544c6d)) + ugfx.area(0,0,240,35,ugfx.html_color(0x41476d)) + ugfx.text(5,5,str(data["score"])+"++ " + data["user_username"] + ":",ugfx.BLACK) + + ugfx.text(5,20,"Page: " + str(page+1) + "/" + str(len(screens)),ugfx.BLACK) + count = 0 + for line in screens[page]: + ugfx.text(5,35+count*15,line,ugfx.BLACK) + count+=1 + hold_btn = True + while hold_btn: + if tilda.Buttons.is_pressed(tilda.Buttons.BTN_Menu): + return + if tilda.Buttons.is_pressed(tilda.Buttons.BTN_A): + skip += 1 + hold_btn = False + hold = False + while tilda.Buttons.is_pressed(tilda.Buttons.BTN_A): + utime.sleep_ms(10) + if tilda.Buttons.is_pressed(tilda.Buttons.JOY_Right): + if page < len(screens)-1: + page += 1 + hold_btn = False + while tilda.Buttons.is_pressed(tilda.Buttons.JOY_Right): + utime.sleep_ms(10) + if tilda.Buttons.is_pressed(tilda.Buttons.JOY_Left): + if page > 0: + page -= 1 + hold_btn = False + while tilda.Buttons.is_pressed(tilda.Buttons.JOY_Left): + utime.sleep_ms(10) + + +ugfx_helper.init() +ugfx.clear() +ugfx.text(5,5, "DevRant for the TiLDA Mk4", ugfx.BLACK) +ugfx.text(5, 40, "Connecting To WIFI", ugfx.BLACK) +wifi.connect() +ugfx.text(5, 40, "Connecting To WIFI", ugfx.WHITE) +loop() +app.restart_to_default() diff --git a/LED_Party/main.py b/LED_Party/main.py index a6c508d..e144681 100644 --- a/LED_Party/main.py +++ b/LED_Party/main.py @@ -1,6 +1,6 @@ """ starts an LED party on your badge """ -___name___ = "LED Party (Party Party)" +___title___ = "LED Party (Party Party)" ___license___ = "MIT" ___dependencies___ = ["wifi", "http", "ugfx_helper", "sleep"] ___categories___ = ["LEDs"] diff --git a/avatar/main.py b/avatar/main.py index e88fe6a..ddf021f 100644 --- a/avatar/main.py +++ b/avatar/main.py @@ -1,6 +1,6 @@ """A simple homescreen diplaying an avatar from an url and the user's name""" -___name___ = "Avatar Homescreen" +___title___ = "Avatar Homescreen" ___license___ = "WTFPL" ___categories___ = ["Homescreens"] ___dependencies___ = ["homescreen", "wifi", "http", "sleep", "app", "buttons"] diff --git a/badge_store/main.py b/badge_store/main.py index 0fa0a47..5708631 100644 --- a/badge_store/main.py +++ b/badge_store/main.py @@ -13,6 +13,7 @@ ___bootstrapped___ = True import ugfx_helper, os, database, wifi, app, ospath from dialogs import * from lib.badge_store import BadgeStore +from app import * ### VIEWS ### @@ -29,10 +30,10 @@ def clear(): def show_categories(): clear() - with WaitingMessage(): + with WaitingMessage(title=title, text="Loading categories..."): menu_items = [{"title": c, "category": c} for c in store.get_categories()] - option = prompt_option(menu_items, none_text="Back", text="Categories", title=title) + option = prompt_option(menu_items, none_text="Back", title="Install: Categories") if option: show_apps(option["category"]) @@ -43,51 +44,70 @@ def show_apps(c): clear() menu_items = [{"title": a, "app": a} for a in store.get_apps(c)] - option = prompt_option(menu_items, none_text="Back", title=title) + option = prompt_option(menu_items, none_text="Back", title="Install: " + c) if option: - show_app(option["app"]) + show_app(option["app"],c) else: - return + show_categories() -def show_app(a): +def show_app(a,c): clear() - with WaitingMessage(): + with WaitingMessage(title=title, text="Loading app description..."): app_info = store.get_app(a) - install = prompt_boolean(app_info["description"], title=a, true_text="Install", false_text="Back") + + # Try to get the 'title' key from app_info, falling back to the value of a if not present + name = app_info.get("title", a) + desc = app_info["description"].strip() + app_text = """App:\n{}\n\nDescription:\n{}""".format(name, desc) + install = prompt_boolean(app_text , title="Install App", true_text="Install", false_text="Back") if install: - with WaitingMessage(title="Installing %s" % a, text="Please wait...") as message: - installers = store.install(_get_current_apps() + [a]) + app_text = "App:\n{}\n\n".format(name) + with WaitingMessage(title="Installing App...", text="%sGetting ready..." % app_text) as message: + installers = store.install([a]) n = len(installers) for i, installer in enumerate(installers): - message.text = "%s (%s/%s)" % (installer.path, i + 1, n) + message.text = "%s%s (%s/%s)" % (app_text + "Downloading files...\n\n", installer.path, i + 1, n) installer.download() app.uncache_apps() - notice("App %s has been successfully installed" % a, title=title, close_text="Back") + launch = prompt_boolean( + "%sSuccessfully installed.\n\nPress A to launch the app.\n\nPress B to list more \"%s\" apps." % (app_text, c), title="Install Success!", true_text="Launch", false_text="Back") + if (launch): + for app_obj in get_apps(): + if app_obj.name == a: + app_obj.boot() + else: + show_apps(c) + else: + show_apps(c) + def show_update(): clear() - update = prompt_boolean("Do you want to update all apps on this badge?", title="Update", true_text="OK", false_text="Back") + update = prompt_boolean("Do you want to update all apps on this badge?", title="Update all Apps", true_text="OK", false_text="Back") if update: clear() - with WaitingMessage(title=title, text="Please wait...") as message: + with WaitingMessage(title=title, text="Getting updates...") as message: + update_text = "Downloading files:" installers = store.install(_get_current_apps()) n = len(installers) for i, installer in enumerate(installers): - message.text = "%s (%s/%s)" % (installer.path, i + 1, n) + message.text = "%s\n\n%s (%s/%s)" % (update_text, installer.path, i + 1, n) installer.download() - notice("Your badge has been successfully updated", title=title, close_text="Back") + notice("Your badge has been successfully updated.", title="Update Success!", close_text="Back") def show_remove(): clear() - app_to_remove = prompt_option(_get_current_apps(), none_text="Back", text="Select App to remove") + app_to_remove = prompt_option(_get_current_apps(), title="Remove App...", none_text="Back", text="Select an App to remove.") if app_to_remove: ospath.recursive_rmdir(app_to_remove) app.uncache_apps() - notice("%s has been removed" % app_to_remove, title=title, close_text="Back") + + app_text = """App:\n{}""".format(app_to_remove) + notice("\"%s\"\n\nThe app has now been removed." % app_text, title="Remove Success!", close_text="Back") def main_menu(): while True: diff --git a/badgesimulator/main.py b/badgesimulator/main.py index ccfc8a4..33b0bd7 100644 --- a/badgesimulator/main.py +++ b/badgesimulator/main.py @@ -1,6 +1,6 @@ """This app creates a real EMF badge experience""" -___name___ = "EMF 2018 badge simulator" +___title___ = "EMF 2018 badge simulator" ___license___ = "MIT" ___categories___ = ["EMF"] ___dependencies___ = ["sleep", "app"] diff --git a/basic_clock/main.py b/basic_clock/main.py index 7d2b8b4..3ed86b6 100644 --- a/basic_clock/main.py +++ b/basic_clock/main.py @@ -1,5 +1,6 @@ """An NTP time app""" -___name___ = "NTP time" + +___title___ = "NTP time" ___license___ = "MIT" ___dependencies___ = ["ntp", "wifi", "app"] ___categories___ = ["EMF"] diff --git a/beer/main.py b/beer/main.py index 92c3d4f..78c894d 100644 --- a/beer/main.py +++ b/beer/main.py @@ -2,7 +2,7 @@ Get up to date information on what's in stock at The Robot Arms! """ -___name___ = "beer" +___title___ = "beer" ___license___ = "MIT" ___dependencies___ = ["app", "sleep", "wifi", "http", "ugfx_helper"] ___categories___ = ["EMF"] diff --git a/bf-interpreter/main.py b/bf-interpreter/main.py new file mode 100644 index 0000000..c409026 --- /dev/null +++ b/bf-interpreter/main.py @@ -0,0 +1,208 @@ +"""Simple brainfuck (an esoteric programming language) interpreter. + +Runs very slowly... prints sierpinski triangle""" + +___name___ = "bf interpreter" +___license___ = "MIT" +___dependencies___ = ["sleep", "app"] +___categories___ = ["Other"] + +import ugfx, os, time, sleep, app +from tilda import Buttons +from time import sleep_ms + +# initialize screen +ugfx.init() +ugfx.clear() + +ugfx.set_default_font(ugfx.FONT_TITLE) + + +Prog=""" + +>-[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>++[-]->>>>+>>>+>>>+>>>+>>>+> + >>+>>>+>>>+>>>++[-<+]-<<<<+<<++++[->++++++++<]<++[------>+<]>++<<+[--->++<]>++ +<<-[--->+<]>------<+[-<+]-<[>>+[->+]-<[-]<<[-]+++[>[-]++++++++++.[-]+++[>+[>>+<< +-]>>[<<++[-<+]->++[->+]->-]<<+[-<+]->-[-[-[-[-[-[-[-[->>>]>>>]>>>]>>>]>>>]>>>]>> +>]>>>]>>>>>>>>>>>>>>>>>>>>> > > > > > > > > > > > > >>>>>>>>>>[+[-<+]-<<<<<<.>>> +>>>>]>[+[-<+]-<<<< <<<.>>>>>>>>]>[+[- +<+]-<<<<<<<<.>>> tic tac toe >>>>>>]+[-<+]-<< +<<<.>>>-]<-]+++ to play: type a number (1 to 9) to +++++++.[-]<<<< +<<[<<<<<<<<<<<+ place an X at that grid location [--->++<]>+++.[ +->+++++++<]>.++ ++++.-[---->+<] +>+++.---[->+++<] [ http://mitxela.com/ ] >.+++[->++++<]>+ +.+++++.-[->+++++<] >.[--->+<]>-.+[-<+ +]-<[-]>>>>]<[<<<<<<<++++[++++>---<]>+.[++++>---<]>-.+++[->+++<]>++.+[--->+<]>+.+ +[---->+<]>+++.[--->+<]>-.[-]+[-<+]-<[-]>>>>]<[<<<<<<<<<<+[--->++<]>+++.[->++++++ ++<]>.++++++.-[---->+<]>+++.++++++[->++<]>.+[--->+<]>.++++.++++[->+++<]>.--[--->+ +<]>.[--->+<]>-.+[-<+]-<[-]>>>>]<+[-<+]-<[>>->>>>>>+[-<<<<[-]<<[-]>>>>-[>>[-]+<<+ +<[-]<[-]<[-]<[-]-[----->+<]>---<,>[-<->]<[>>+>+<<<-]>>[<<+>>-]+++++++++[->-[<<]> +]>>-]<<<<[-]>>>>[-]+<<<<<<[>>+>+<<<-]>>[<<+>>-]>>]>>-<<<[-]<<[<->-]<-[-[-[-[-[-[ +-[-[->>>]>>>]>>>]>>>]>>>]>>>]>>>]>>>]]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>[->++[-<+]->>>>>[>>>[>>>[+[-<+]-<<<<<<<<<[-]++[->+]->]]]+[-<+]->>>>>>>>>>>>>>[> +>>[>>>[+[-<+]-<<<<<<<<<[-]++[->+]->]]]+[-<+]->>>>>>>>>>>>>>>>>>>>>>>[>>>[>>>[+[- +<+]-<<<<<<<<<[-]++[->+]->]]]+[-<+]->>>>>[>>>>>>>>>[>>>>>>>>>[+[-<+]-<<<<<<<<<[-] +++[->+]->]]]+[-<+]->>>>>>>>[>>>>>>>>>[>>>>>>>>>[+[-<+]-<<<<<<<<<[-]++[->+]->]]]+ +[-<+]->>>>>>>>>>>[>>>>>>>>>[>>>>>>>>>[+[-<+]-<<<<<<<<<[-]++[->+]->]]]+[-<+]->>>> +>[>>>>>>>>>>>>[>>>>>>>>>>>>[+[-<+]-<<<<<<<<<[-]++[->+]->]]]+[-<+]->>>>>>>>>>>[>> +>>>>[>>>>>>[+[-<+]-<<<<<<<<<[-]++[->+]->]]]+[-<+]-<<<<<<<<<-[++[->+]-<<<<<<<<<<[ +-]++[->+]->>>>[+[-<+]-<<<<<<<<<<[-]+[->+]->]+[-<+]->>>>>>>[+[-<+]-<<<<<<<<<<[-]+ +[->+]->]+[-<+]->>>>>>>>>>[+[-<+]-<<<<<<<<<<[-]+[->+]->]+[-<+]->>>>>>>>>>>>>[+[-< ++]-<<<<<<<<<<[-]+[->+]->]+[-<+]->>>>>>>>>>>>>>>>[+[-<+]-<<<<<<<<<<[-]+[->+]->]+[ +-<+]->>>>>>>>>>>>>>>>>>>[+[-<+]-<<<<<<<<<<[-]+[->+]->]+[-<+]->>>>>>>>>>>>>>>>>>> +>>>[+[-<+]-<<<<<<<<<<[-]+[->+]->]+[-<+]->>>>>>>>>>>>>>>>>>>>>>>>>[+[-<+]-<<<<<<< +<<<[-]+[->+]->]+[-<+]->>>>>>>>>>>>>>>>>>>>>>>>>>>>[+[-<+]-<<<<<<<<<<[-]+[->+]->] ++[-<+]-<<[-]>[-]+>>>>>>[>>>[>>[+[-<+]-<[-]<[-]++++[->+]->]]]>+[-<+]->>>>>[>>[>>> +>[+[-<+]-<[-]<[-]+++[->+]->]]]>+[-<+]->>>>[>>>>[>>>[+[-<+]-<[-]<[-]++[->+]->]]]> ++[-<+]->>>>>>>>>>>>>>[>>>[>>[+[-<+]-<[-]<[-]+++++++[->+]->]]]>+[-<+]->>>>>>>>>>> +>>>[>>[>>>>[+[-<+]-<[-]<[-]++++++[->+]->]]]>+[-<+]->>>>>>>>>>>>>[>>>>[>>>[+[-<+] +-<[-]<[-]+++++[->+]->]]]>+[-<+]->>>>>>>>>>>>>>>>>>>>>>>[>>>[>>[+[-<+]-<[-]<[-]++ +++++++++[->+]->]]]>+[-<+]->>>>>>>>>>>>>>>>>>>>>>>[>>[>>>>[+[-<+]-<[-]<[-]+++++++ +++[->+]->]]]>+[-<+]->>>>>>>>>>>>>>>>>>>>>>[>>>>[>>>[+[-<+]-<[-]<[-]++++++++[->+] +->]]]>+[-<+]->>>>>[>>>>>>>>>[>>>>>>>>[+[-<+]-<[-]<[-]++++++++[->+]->]]]>+[-<+]-> +>>>>[>>>>>>>>[>>>>>>>>>>[+[-<+]-<[-]<[-]+++++[->+]->]]]>+[-<+]->>>>[>>>>>>>>>>[> +>>>>>>>>[+[-<+]-<[-]<[-]++[->+]->]]]>+[-<+]->>>>>>>>[>>>>>>>>>[>>>>>>>>[+[-<+]-< +[-]<[-]+++++++++[->+]->]]]>+[-<+]->>>>>>>>[>>>>>>>>[>>>>>>>>>>[+[-<+]-<[-]<[-]++ +++++[->+]->]]]>+[-<+]->>>>>>>[>>>>>>>>>>[>>>>>>>>>[+[-<+]-<[-]<[-]+++[->+]->]]]> ++[-<+]->>>>>>>>>>>[>>>>>>>>>[>>>>>>>>[+[-<+]-<[-]<[-]++++++++++[->+]->]]]>+[-<+] +->>>>>>>>>>>[>>>>>>>>[>>>>>>>>>>[+[-<+]-<[-]<[-]+++++++[->+]->]]]>+[-<+]->>>>>>> +>>>[>>>>>>>>>>[>>>>>>>>>[+[-<+]-<[-]<[-]++++[->+]->]]]>+[-<+]->>>>>[>>>>>>>>>>>> +[>>>>>>>>>>>[+[-<+]-<[-]<[-]++++++++++[->+]->]]]>+[-<+]->>>>[>>>>>>>>>>>>>[>>>>> +>>>>>>>[+[-<+]-<[-]<[-]++[->+]->]]]>+[-<+]->>>>>>>>>>>[>>>>>>[>>>>>[+[-<+]-<[-]< +[-]++++++++[->+]->]]]>+[-<+]->>>>>>>>>>[>>>>>>>[>>>>>>[+[-<+]-<[-]<[-]++++[->+]- +>]]]>+[-<+]->>>>>>[>>>[>[+[-<+]-<[-]<[-]++++[->+]->]]]>+[-<+]->>>>>>[>[>>>>>[+[- +<+]-<[-]<[-]+++[->+]->]]]>+[-<+]->>>>[>>>>>[>>>[+[-<+]-<[-]<[-]++[->+]->]]]>+[-< ++]->>>>>>>>>>>>>>>[>>>[>[+[-<+]-<[-]<[-]+++++++[->+]->]]]>+[-<+]->>>>>>>>>>>>>>> +[>[>>>>>[+[-<+]-<[-]<[-]++++++[->+]->]]]>+[-<+]->>>>>>>>>>>>>[>>>>>[>>>[+[-<+]-< +[-]<[-]+++++[->+]->]]]>+[-<+]->>>>>>>>>>>>>>>>>>>>>>>>[>>>[>[+[-<+]-<[-]<[-]++++ +++++++[->+]->]]]>+[-<+]->>>>>>>>>>>>>>>>>>>>>>>>[>[>>>>>[+[-<+]-<[-]<[-]++++++++ ++[->+]->]]]>+[-<+]->>>>>>>>>>>>>>>>>>>>>>[>>>>>[>>>[+[-<+]-<[-]<[-]++++++++[->+] +->]]]>+[-<+]->>>>>>[>>>>>>>>>[>>>>>>>[+[-<+]-<[-]<[-]++++++++[->+]->]]]>+[-<+]-> +>>>>>[>>>>>>>[>>>>>>>>>>>[+[-<+]-<[-]<[-]+++++[->+]->]]]>+[-<+]->>>>[>>>>>>>>>>> +[>>>>>>>>>[+[-<+]-<[-]<[-]++[->+]->]]]>+[-<+]->>>>>>>>>[>>>>>>>>>[>>>>>>>[+[-<+] +-<[-]<[-]+++++++++[->+]->]]]>+[-<+]->>>>>>>>>[>>>>>>>[>>>>>>>>>>>[+[-<+]-<[-]<[- +]++++++[->+]->]]]>+[-<+]->>>>>>>[>>>>>>>>>>>[>>>>>>>>>[+[-<+]-<[-]<[-]+++[->+]-> +]]]>+[-<+]->>>>>>>>>>>>[>>>>>>>>>[>>>>>>>[+[-<+]-<[-]<[-]++++++++++[->+]->]]]>+[ +-<+]->>>>>>>>>>>>[>>>>>>>[>>>>>>>>>>>[+[-<+]-<[-]<[-]+++++++[->+]->]]]>+[-<+]->> +>>>>>>>>[>>>>>>>>>>>[>>>>>>>>>[+[-<+]-<[-]<[-]++++[->+]->]]]>+[-<+]->>>>>>[>>>>> +>>>>>>>[>>>>>>>>>>[+[-<+]-<[-]<[-]++++++++++[->+]->]]]>+[-<+]->>>>[>>>>>>>>>>>>> +>[>>>>>>>>>>>>[+[-<+]-<[-]<[-]++[->+]->]]]>+[-<+]->>>>>>>>>>>>[>>>>>>[>>>>[+[-<+ +]-<[-]<[-]++++++++[->+]->]]]>+[-<+]->>>>>>>>>>[>>>>>>>>[>>>>>>[+[-<+]-<[-]<[-]++ +++[->+]->]]]>+[-<+]-<[>>+[-<+]->>>>>>>>>>>>>>>>>>>>>>>>>>>>[+[-<+]-<[-]<[-]+++++ ++++++[->+]->]+[-<+]->>>>>>>>>>>>>>>>>>>>>>[+[-<+]-<[-]<[-]++++++++[->+]->]+[-<+] +->>>>>>>>>>[+[-<+]-<[-]<[-]++++[->+]->]+[-<+]->>>>[+[-<+]-<[-]<[-]++[->+]->]+[-< ++]->>>>>>>>>>>>>>>>>>>>>>>>>[+[-<+]-<[-]<[-]+++++++++[->+]->]+[-<+]->>>>>>>>>>>> +>>>>>>>[+[-<+]-<[-]<[-]+++++++[->+]->]+[-<+]->>>>>>>>>>>>>[+[-<+]-<[-]<[-]+++++[ +->+]->]+[-<+]->>>>>>>[+[-<+]-<[-]<[-]+++[->+]->]+[-<+]->>>>>>>>>>>>>>>>[+[-<+]-< +[-]<[-]++++++[->+]->]+[-<+]->]>>+[-<+]-<<<<[+[->+]->>>>>>>>>>>>>>>>>[+[-<+]-<[-] +<[-]++[->+]->]+[-<+]->]>>>>+[-<+]-<<[>>>+[-<+]-<[-]<[+[-<+]->++[->+]-<<-]+[-<+]- +>-[-[-[-[-[-[-[-[->>>]>>>]>>>]>>>]>>>]>>>]>>>]>>>]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +>>>>>>>>>>>>>->>++[-<+]->]>>>>+[-<+]-<<[-]>>>+[-<+]-<<<<[-]>>>>>+[-<+]->>>>>>[>> +>[>>>[+[-<+]-<<<<<<<<<<<[-]++[->+]->]]]+[-<+]->>>>>>>>>>>>>>>[>>>[>>>[+[-<+]-<<< +<<<<<<<<[-]++[->+]->]]]+[-<+]->>>>>>>>>>>>>>>>>>>>>>>>[>>>[>>>[+[-<+]-<<<<<<<<<< +<[-]++[->+]->]]]+[-<+]->>>>>>[>>>>>>>>>[>>>>>>>>>[+[-<+]-<<<<<<<<<<<[-]++[->+]-> +]]]+[-<+]->>>>>>>>>[>>>>>>>>>[>>>>>>>>>[+[-<+]-<<<<<<<<<<<[-]++[->+]->]]]+[-<+]- +>>>>>>>>>>>>[>>>>>>>>>[>>>>>>>>>[+[-<+]-<<<<<<<<<<<[-]++[->+]->]]]+[-<+]->>>>>>[ +>>>>>>>>>>>>[>>>>>>>>>>>>[+[-<+]-<<<<<<<<<<<[-]++[->+]->]]]+[-<+]->>>>>>>>>>>>[> + >>>>>[>>>>>>[+[-<+]-<<<<<<<<<<<[-]++[->+]->]]]+[-<+]-<[-]]++[->+]->]+[-<+]-<+[ + -<+]-<]>>+[->+]->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+[-[-]<+]-<+[-[-]<+]-<+>] +""" + + + +# Hello World +#Prog="++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++." + +# Sierpinski +Prog=""" +++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[ + -<<<[ + ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<< + ]>.>+[>>]>+ +]""" + + + + +buf="" +def output(t): + global buf + buf+=t + buf=buf[-(16*80):] + ugfx.clear() + lines=buf.split("\n") + lines=lines[-16:] + for i,v in enumerate(lines): + ugfx.text(5,i*20+5, v+" ", ugfx.BLACK) + +lastpushed=0 +def pushed(n): + global Tape, TP, waiting + if (waiting): + output(n+" \n") + Tape[TP]=ord(n) + waiting=False + + + +Buttons.enable_interrupt(Buttons.BTN_1, lambda button_id:pushed("1"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_2, lambda button_id:pushed("2"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_3, lambda button_id:pushed("3"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_4, lambda button_id:pushed("4"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_5, lambda button_id:pushed("5"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_6, lambda button_id:pushed("6"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_7, lambda button_id:pushed("7"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_8, lambda button_id:pushed("8"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_9, lambda button_id:pushed("9"), on_press=True, on_release=False) +Buttons.enable_interrupt(Buttons.BTN_0, lambda button_id:pushed("0"), on_press=True, on_release=False) + + + +output("Loading...") + + + + +waiting=False +Prog+='\0' +Tape=[0]*256 +PP=-1 +TP=0 +while True: + if (waiting): + sleep_ms(200) + else: + PP=PP+1 + if (PP>=len(Prog)): + waiting=True + output("END!") + elif (Prog[PP]=="+"): + Tape[TP]=Tape[TP]+1 + elif (Prog[PP] =="-"): + Tape[TP]=Tape[TP]-1 + elif (Prog[PP] ==">"): + TP=TP+1 + elif (Prog[PP] =="<"): + TP=TP-1 + elif (Prog[PP] =="."): + output(chr(Tape[TP])) + elif (Prog[PP] ==","): + waiting=True + elif (Prog[PP] =="["): + if (Tape[TP]==0): + depth=1 + while (depth>0): + PP=PP+1 + if (Prog[PP]=="]"): + depth = depth - 1 + if (Prog[PP]=="["): + depth = depth + 1 + elif (Prog[PP] =="]"): + if (Tape[TP]!=0): + depth=1 + while (depth>0): + PP=PP-1 + if (Prog[PP]=="]"): + depth = depth + 1 + if (Prog[PP]=="["): + depth = depth - 1 + diff --git a/bluetooth_speaker/main.py b/bluetooth_speaker/main.py new file mode 100644 index 0000000..32c8c17 --- /dev/null +++ b/bluetooth_speaker/main.py @@ -0,0 +1,141 @@ +"""App to use the badge as a (handset profile only) bluetooth speaker""" + +___name___ = "Bluetooth Speaker" +___license___ = "MIT" +___dependencies___ = ["ugfx_helper", "sim800", "dialogs", "buttons", "app"] +___categories___ = ["Sound"] + + +import ugfx_helper, ugfx +import app +import sim800 +from dialogs import * +import buttons + +BLUETOOTH_NAME = "BadgeSpeaker" + +g_paired = False + + +def pairing_dialog(scan_timeout_s=10): + ''' Show BLE devices to pair with and connect. Returns True if paired, False if failed ''' + waiting_message = WaitingMessage("Scanning for bluetooth devices for %s seconds"%scan_timeout_s, "Scanning") + + + devices = sim800.btscan(int(scan_timeout_s * 1000)) + + waiting_message.destroy() + + # List format is [id, name, addr, rssi]. FIXME: Only returns 1 item? + try: + devices_prompts = [{'title': v[1], 'id': v[0]} for v in devices] + except TypeError: #Only one device found. #TODO: Not very neat + devices_prompts = [{'title':devices[1] ,'id':devices[0]},] + + #TODO: Fix non printable chars in device names + + option = prompt_option(devices_prompts, title="Devices Found", select_text="Select", none_text="Rescan") + + if option: + sim800.btpair(option['id']) + passcode = sim800.btparingpasscode() + correct_passcode = prompt_boolean(passcode, title="Started connection from other device?", font=FONT_MEDIUM_BOLD) + + if correct_passcode: + sim800.btpairconfirm() #TODO: 4 number passcodes? + return True + + else: + sim800.btpairreject() + return False + else: + return False + + +def pairing_callback(param): + ''' Callback for incoming pairing request ''' + global g_paired + accept = prompt_boolean("Accept pairing request from %s"%param, title="Incoming pairing") + if accept: + sim800.btpairconfirm(0000) + # Check if we did pair + if len(sim800.btpaired()) > 1: + g_paired = True + else: + sim800.btpairreject() + + +def set_simple_pairing(): + ''' Set pairing mode to 4 digit pin, default 0000 ''' + sim800.command("AT+BTPAIRCFG=1,0000", 1000, "OK") # TODO: Error checking? + + +#Initialise +ugfx_helper.init() +ugfx.init() +ugfx.clear() + +ugfx.text(5,5, "Powering Up SIM800", ugfx.BLACK) +sim800.poweron() +ugfx.clear() + +ugfx.text(5,5, "Enabling Bluetooth", ugfx.BLACK) +sim800.btpoweron() +sim800.btname(BLUETOOTH_NAME) +sim800.poweroff() +sim800.poweron() +sim800.btpoweron() # Needs a full cycle to have an effect +sim800.btvisible(True) + +# Set pairing mode +set_simple_pairing() + +ugfx.text(5,20, "Addr: %s " % sim800.btaddress(), ugfx.BLACK) +ugfx.text(5,35, "Name: %s " % sim800.btname(), ugfx.BLACK) +ugfx.clear() + +# Register pairings callback +sim800.registercallback("+BTPAIRING:", pairing_callback) + +clear_pairing = prompt_boolean("Delete all bluetooth pairings?",title="Clear Pairings?", true_text="Yes", false_text="No") + +if clear_pairing: + sim800.btunpair(0) #0 = clear every pairing + +# Start main loop +ugfx.clear() +ugfx.Label(5,5, 220, 200, "Connect to %s \n Passcode = 0000 \n Press menu to exit" % BLUETOOTH_NAME) + +connected = True + +while(True): + + # Check for pairing button + if (buttons.is_triggered(buttons.Buttons.BTN_1)): + pairing_dialog() + + # Check for exit button + if (buttons.is_triggered(buttons.Buttons.BTN_Menu)): + sim800.btpoweroff() + app.restart_to_default() + + num_connections = len(sim800.btconnected()) + + if (connected == False) and (num_connections > 0): # Gained connection + ugfx.area(0,220,240,320, ugfx.BLACK) #Blank bottom of screen + print(sim800.btconnected()) + sim800.speakervolume(100) + sim800.btvoicevolume(100) + ugfx.set_default_font(ugfx.FONT_TITLE) + ugfx.text(5,230,"CONNECTED!", ugfx.GREEN) + ugfx.set_default_font(ugfx.FONT_SMALL) + connected = True + + elif (connected == True) and (num_connections == 0): # Lost connection + ugfx.area(0,220,240,320, ugfx.BLACK) #Blank bottom of screen + ugfx.set_default_font(ugfx.FONT_TITLE) + ugfx.text(5,230,"DISCONNECTED", ugfx.RED) + ugfx.set_default_font(ugfx.FONT_SMALL) + connected = False + + sleep.wfi() \ No newline at end of file diff --git a/breakout/main.py b/breakout/main.py index 5829ec4..f2fbd87 100644 --- a/breakout/main.py +++ b/breakout/main.py @@ -1,9 +1,9 @@ """Breakout!""" -___name___ = "Breakout" +___title___ = "Breakout" ___license___ = "MIT" ___categories___ = ["Games"] -___dependencies___ = ["app", "ugfx_helper", "random", "buttons"] +___dependencies___ = ["app", "ugfx_helper", "buttons"] from tilda import Buttons import ugfx, ugfx_helper, dialogs diff --git a/btscan/main.py b/btscan/main.py index 787e227..bf72955 100644 --- a/btscan/main.py +++ b/btscan/main.py @@ -1,6 +1,6 @@ """Scan for and display nearby bluetooth devices""" -___name___ = "Bluetooth Scan" +___title___ = "Bluetooth Scan" ___license___ = "MIT" ___dependencies___ = ["sleep", "app", "sim800"] ___categories___ = ["Other", "System"] diff --git a/cards_against_emf/cards.json b/cards_against_emf/cards.json new file mode 100644 index 0000000..2711b2a --- /dev/null +++ b/cards_against_emf/cards.json @@ -0,0 +1,4272 @@ +{ + "blackCards": [ + { + "text": "Why can't I sleep at night?", + "pick": 1 + }, + { + "text": "I got 99 problems but _ ain't one.", + "pick": 1 + }, + { + "text": "What's a girl's best friend?", + "pick": 1 + }, + { + "text": "What's that smell?", + "pick": 1 + }, + { + "text": "This is the way the world ends / This is the way the world ends / Not with a bang but with _.", + "pick": 1 + }, + { + "text": "What is Batman's guilty pleasure?", + "pick": 1 + }, + { + "text": "TSA guidelines now prohibit _ on airplanes.", + "pick": 1 + }, + { + "text": "What ended my last relationship?", + "pick": 1 + }, + { + "text": "MTV's new reality show features eight washed-up celebrities living with _.", + "pick": 1 + }, + { + "text": "I drink to forget _.", + "pick": 1 + }, + { + "text": "I'm sorry, Professor, but I couldn't complete my homework because of _.", + "pick": 1 + }, + { + "text": "Alternative medicine is now embracing the curative powers of _.", + "pick": 1 + }, + { + "text": "What's that sound?", + "pick": 1 + }, + { + "text": "What's the next Happy Meal® toy?", + "pick": 1 + }, + { + "text": "It's a pity that kids these days are all getting involved with _.", + "pick": 1 + }, + { + "text": "In the new Disney Channel Original Movie, Hannah Montana struggles with _ for the first time.", + "pick": 1 + }, + { + "text": "_. That's how I want to die.", + "pick": 1 + }, + { + "text": "What does Dick Cheney prefer?", + "pick": 1 + }, + { + "text": "What's the most emo?", + "pick": 1 + }, + { + "text": "Instead of coal, Santa now gives the bad children _.", + "pick": 1 + }, + { + "text": "Next from J.K. Rowling: Harry Potter and the Chamber of _.", + "pick": 1 + }, + { + "text": "A romantic, candlelit dinner would be incomplete without _.", + "pick": 1 + }, + { + "text": "White people like _.", + "pick": 1 + }, + { + "text": "_. Betcha can't have just one!", + "pick": 1 + }, + { + "text": "War!

What is it good for?", + "pick": 1 + }, + { + "text": "BILLY MAYS HERE FOR _.", + "pick": 1 + }, + { + "text": "_. High five, bro.", + "pick": 1 + }, + { + "text": "During sex, I like to think about _.", + "pick": 1 + }, + { + "text": "What did I bring back from Mexico?", + "pick": 1 + }, + { + "text": "What are my parents hiding from me?", + "pick": 1 + }, + { + "text": "What will always get you laid?", + "pick": 1 + }, + { + "text": "What would grandma find disturbing, yet oddly charming?", + "pick": 1 + }, + { + "text": "What did the U.S. airdrop to the children of Afghanistan?", + "pick": 1 + }, + { + "text": "What helps Obama unwind?", + "pick": 1 + }, + { + "text": "What's there a ton of in heaven?", + "pick": 1 + }, + { + "text": "Major League Baseball has banned _ for giving players an unfair advantage.", + "pick": 1 + }, + { + "text": "When I am a billionaire, I shall erect a 50-foot statue to commemorate _.", + "pick": 1 + }, + { + "text": "What's the new fad diet?", + "pick": 1 + }, + { + "text": "When I am the President of the United States, I will create the Department of _.", + "pick": 1 + }, + { + "text": "_. It's a trap!", + "pick": 1 + }, + { + "text": "How am I maintaining my relationship status?", + "pick": 1 + }, + { + "text": "What will I bring back in time to convince people that I am a powerful wizard?", + "pick": 1 + }, + { + "text": "While the United States raced the Soviet Union to the moon, the Mexican government funneled millions of pesos into research on _.", + "pick": 1 + }, + { + "text": "Coming to Broadway this season, _: The Musical.", + "pick": 1 + }, + { + "text": "What's my secret power?", + "pick": 1 + }, + { + "text": "What gives me uncontrollable gas?", + "pick": 1 + }, + { + "text": "But before I kill you, Mr. Bond, I must show you _.", + "pick": 1 + }, + { + "text": "What never fails to liven up the party?", + "pick": 1 + }, + { + "text": "What am I giving up for Lent?", + "pick": 1 + }, + { + "text": "What do old people smell like? ", + "pick": 1 + }, + { + "text": "The class field trip was completely ruined by _.", + "pick": 1 + }, + { + "text": "When Pharaoh remained unmoved, Moses called down a plague of _.", + "pick": 1 + }, + { + "text": "I do not know with which weapons World War III will be fought, but World War IV will be fought with _.", + "pick": 1 + }, + { + "text": "What's Teach for America using to inspire inner city students to succeed?", + "pick": 1 + }, + { + "text": "In Michael Jackson's final moments, he thought about _.", + "pick": 1 + }, + { + "text": "Why do I hurt all over?", + "pick": 1 + }, + { + "text": "Studies show that lab rats navigate mazes 50% faster after being exposed to _.", + "pick": 1 + }, + { + "text": "Why am I sticky?", + "pick": 1 + }, + { + "text": "What's my anti-drug?", + "pick": 1 + }, + { + "text": "And the Academy Award for _ goes to _.", + "pick": 2 + }, + { + "text": "For my next trick, I will pull _ out of _.", + "pick": 2 + }, + { + "text": "_: Good to the last drop.", + "pick": 1 + }, + { + "text": "What did Vin Diesel eat for dinner?", + "pick": 1 + }, + { + "text": "_: kid-tested, mother-approved.", + "pick": 1 + }, + { + "text": "What gets better with age?", + "pick": 1 + }, + { + "text": "I never truly understood _ until I encountered _.", + "pick": 2 + }, + { + "text": "Rumor has it that Vladimir Putin's favorite delicacy is _ stuffed with _.", + "pick": 2 + }, + { + "text": "Lifetime® presents _, the story of _.", + "pick": 2 + }, + { + "text": "Make a haiku.", + "pick": 3 + }, + { + "text": "In M. Night Shyamalan's new movie, Bruce Willis discovers that _ had really been _ all along.", + "pick": 2 + }, + { + "text": "_ is a slippery slope that leads to _.", + "pick": 2 + }, + { + "text": "In a world ravaged by _, our only solace is _.", + "pick": 2 + }, + { + "text": "That's right, I killed _. How, you ask? _.", + "pick": 2 + }, + { + "text": "When I was tripping on acid, _ turned into _.", + "pick": 2 + }, + { + "text": "_ + _ = _.", + "pick": 3 + }, + { + "text": "What's the next superhero/sidekick duo?", + "pick": 2 + }, + { + "text": "Dear Abby,

I'm having some trouble with _ and would like your advice.", + "pick": 1 + }, + { + "text": "After the earthquake, Sean Penn brought _ to the people of Haiti.", + "pick": 1 + }, + { + "text": "In L.A. County Jail, word is you can trade 200 cigarettes for _.", + "pick": 1 + }, + { + "text": "Maybe she's born with it. Maybe it's _.", + "pick": 1 + }, + { + "text": "Life for American Indians was forever changed when the White Man introduced them to _.", + "pick": 1 + }, + { + "text": "Next on ESPN2, the World Series of _.", + "pick": 1 + }, + { + "text": "Step 1: _. Step 2: _. Step 3: Profit.", + "pick": 2 + }, + { + "text": "Here is the church
Here is the steeple
Open the doors
And there is _.", + "pick": 1 + }, + { + "text": "How did I lose my virginity?", + "pick": 1 + }, + { + "text": "During his childhood, Salvador Dalí produced hundreds of paintings of _.", + "pick": 1 + }, + { + "text": "In 1,000 years, when paper money is a distant memory, how will we pay for goods and services?", + "pick": 1 + }, + { + "text": "What don't you want to find in your Kung Pao chicken?", + "pick": 1 + }, + { + "text": "The Smithsonian Museum of Natural History has just opened an exhibit on _.", + "pick": 1 + }, + { + "text": "Daddy, why is Mommy crying?", + "pick": 1 + }, + { + "text": "What brought the orgy to a grinding halt?", + "pick": 1 + }, + { + "text": "When I pooped, what came out of my butt?", + "pick": 1 + }, + { + "text": "In the distant future, historians will agree that _ marked the beginning of America's decline.", + "pick": 1 + }, + { + "text": "What's the gift that keeps on giving?", + "pick": 1 + }, + { + "text": "This season on Man vs. Wild, Bear Grylls must survive in the depths of the Amazon with only _ and his wits.", + "pick": 1 + }, + { + "text": "Michael Bay's new three-hour action epic pits _ against _.", + "pick": 2 + }, + { + "text": "And I would have gotten away with it, too, if it hadn't been for _!", + "pick": 1 + }, + { + "text": "In a pinch, _ can be a suitable substitute for _.", + "pick": 2 + }, + { + "text": "What has been making life difficult at the nudist colony?", + "pick": 1 + }, + { + "text": "Science will never explain the origin of _.", + "pick": 1 + }, + { + "text": "In Rome, there are whisperings that the Vatican has a secret room devoted to _.", + "pick": 1 + }, + { + "text": "I learned the hard way that you can't cheer up a grieving friend with _.", + "pick": 1 + }, + { + "text": "When all else fails, I can always masturbate to _.", + "pick": 1 + }, + { + "text": "An international tribunal has found _ guilty of _.", + "pick": 2 + }, + { + "text": "In its new tourism campaign, Detroit proudly proclaims that it has finally eliminated _.", + "pick": 1 + }, + { + "text": "In his new self-produced album, Kanye West raps over the sounds of _.", + "pick": 1 + }, + { + "text": "The socialist governments of Scandinavia have declared that access to _ is a basic human right.", + "pick": 1 + }, + { + "text": "He who controls _ controls the world.", + "pick": 1 + }, + { + "text": "Dear Sir or Madam, We regret to inform you that the Office of _ has denied your request for _.", + "pick": 2 + }, + { + "text": "The CIA now interrogates enemy agents by repeatedly subjecting them to _.", + "pick": 1 + }, + { + "text": "_ would be woefully incomplete without _.", + "pick": 2 + }, + { + "text": "During his midlife crisis, my dad got really into _.", + "pick": 1 + }, + { + "text": "Before I run for president, I must destroy all evidence of my involvement with _.", + "pick": 1 + }, + { + "text": "My new favorite porn star is Joey \"_\" McGee.", + "pick": 1 + }, + { + "text": "In his newest and most difficult stunt, David Blaine must escape from _.", + "pick": 1 + }, + { + "text": "This is your captain speaking. Fasten your seatbelts and prepare for _.", + "pick": 1 + }, + { + "text": "My mom freaked out when she looked at my browser history and found _.com/_.", + "pick": 2 + }, + { + "text": "The Five Stages of Grief: denial, anger, bargaining, _, acceptance.", + "pick": 1 + }, + { + "text": "Members of New York's social elite are paying thousands of dollars just to experience _.", + "pick": 1 + }, + { + "text": "I went from _ to _, all thanks to _.", + "pick": 3 + }, + { + "text": "Little Miss Muffet Sat on a tuffet, Eating her curds and _.", + "pick": 1 + }, + { + "text": "This month's Cosmo: \"Spice up your sex life by bringing _ into the bedroom.\"", + "pick": 1 + }, + { + "text": "If God didn't want us to enjoy _, he wouldn't have given us _.", + "pick": 2 + }, + { + "text": "My country, 'tis of thee, sweet land of _.", + "pick": 1 + }, + { + "text": "After months of debate, the Occupy Wall Street General Assembly could only agree on \"More _!\"", + "pick": 1 + }, + { + "text": "I spent my whole life working toward _, only to have it ruined by _.", + "pick": 2 + }, + { + "text": "Next time on Dr. Phil: How to talk to your child about _.", + "pick": 1 + }, + { + "text": "Only two things in life are certain: death and _.", + "pick": 1 + }, + { + "text": "Everyone down on the ground! We don't want to hurt anyone. We're just here for _.", + "pick": 1 + }, + { + "text": "The healing process began when I joined a support group for victims of _.", + "pick": 1 + }, + { + "text": "The votes are in, and the new high school mascot is _.", + "pick": 1 + }, + { + "text": "Charades was ruined for me forever when my mom had to act out _.", + "pick": 1 + }, + { + "text": "Before _, all we had was _.", + "pick": 2 + }, + { + "text": "Tonight on 20/20: What you don't know about _ could kill you.", + "pick": 1 + }, + { + "text": "You haven't truly lived until you've experienced _ and _ at the same time.", + "pick": 2 + }, + { + "text": "Hey baby, come back to my place and I'll show you _.", + "pick": 1 + }, + { + "text": "My gym teacher got fired for adding _ to the obstacle course.", + "pick": 1 + }, + { + "text": "Finally! A service that delivers _ right to your door.", + "pick": 1 + }, + { + "text": "To prepare for his upcoming role, Daniel Day-Lewis immersed himself in the world of _.", + "pick": 1 + }, + { + "text": "My life is ruled by a vicious cycle of _ and _.", + "pick": 2 + }, + { + "text": "During high school, I never really fit in until I found _ club.", + "pick": 1 + }, + { + "text": "Money can't buy me love, but it can buy me _.", + "pick": 1 + }, + { + "text": "Listen, son. If you want to get involved with _, I won't stop you. Just steer clear of _.", + "pick": 2 + }, + { + "text": "A successful job interview begins with a firm handshake and ends with _.", + "pick": 1 + }, + { + "text": "Call the law offices of Goldstein & Goldstein, because no one should have to tolerate _ in the workplace.", + "pick": 1 + }, + { + "text": "Lovin' you is easy 'cause you're _.", + "pick": 1 + }, + { + "text": "The blind date was going horribly until we discovered our shared interest in _.", + "pick": 1 + }, + { + "text": "What left this stain on my couch?", + "pick": 1 + }, + { + "text": "Turns out that _-Man was neither the hero we needed nor wanted.", + "pick": 1 + }, + { + "text": "After months of practice with _, I think I'm finally ready for _.", + "pick": 2 + }, + { + "text": "In the seventh circle of Hell, sinners must endure _ for all eternity.", + "pick": 1 + }, + { + "text": "As part of his daily regimen, Anderson Cooper sets aside 15 minutes for _.", + "pick": 1 + }, + { + "text": "When you get right down to it, _ is just _.", + "pick": 2 + }, + { + "text": "Having problems with _? Try _!", + "pick": 2 + }, + { + "text": "And what did you bring for show and tell?", + "pick": 1 + }, + { + "text": "I'm not like the rest of you. I'm too rich and busy for _.", + "pick": 1 + }, + { + "text": "With enough time and pressure, _ will turn into _.", + "pick": 2 + }, + { + "text": "_: Hours of fun. Easy to use. Perfect for _!", + "pick": 2 + }, + { + "text": "_. Awesome in theory, kind of a mess in practice.", + "pick": 1 + }, + { + "text": "As part of his contract, Prince won't perform without _ in his dressing room.", + "pick": 1 + }, + { + "text": "Man, this is bullshit. Fuck _.", + "pick": 1 + }, + { + "text": "Dear Leader Kim Jong-un,
our village praises your infinite wisdom with a humble offering of _.", + "pick": 1 + }, + { + "text": "_ may pass, but _ will last forever.", + "pick": 2 + }, + { + "text": "She's up all night for good fun.
I'm up all night for _.", + "pick": 1 + }, + { + "text": "Alright, bros. Our frat house is condemned, and all the hot slampieces are over at Gamma Phi. The time has come to commence Operation _.", + "pick": 1 + }, + { + "text": "The Japanese have developed a smaller, more efficient version of _.", + "pick": 1 + }, + { + "text": "In return for my soul, the Devil promised me _, but all I got was _.", + "pick": 2 + }, + { + "text": "You guys, I saw this crazy movie last night. It opens on _, and then there's some stuff about _, and then it ends with _.", + "pick": 3 + }, + { + "text": "_ will never be the same after _.", + "pick": 2 + }, + { + "text": "Wes Anderson's new film tells the story of a precocious child coming to terms with _.", + "pick": 1 + }, + { + "text": "In the beginning, there was _.
And the Lord said, \"Let there be _.\"", + "pick": 2 + }, + { + "text": "What's fun until it gets weird?", + "pick": 1 + }, + { + "text": "We never did find _, but along the way we sure learned a lot about _.", + "pick": 2 + }, + { + "text": "You've seen the bearded lady!
You've seen the ring of fire!
Now, ladies and gentlemen, feast your eyes upon _!", + "pick": 1 + }, + { + "text": "How am I compensating for my tiny penis?", + "pick": 1 + }, + { + "text": "I'm sorry, sir, but we don't allow _ at the country club.", + "pick": 1 + }, + { + "text": "2 AM in the city that never sleeps. The door swings open and she walks in, legs up to here. Something in her eyes tells me she's looking for _.", + "pick": 1 + }, + { + "text": "As king, how will I keep the peasants in line?", + "pick": 1 + }, + { + "text": "Oprah's book of the month is \"_ For _: A Story of Hope.\"", + "pick": 2 + }, + { + "text": "Do not fuck with me! I am literally _ right now.", + "pick": 1 + }, + { + "text": "Adventure.
Romance.
_.

From Paramount Pictures, \"_.\"", + "pick": 2 + }, + { + "text": "I am become _, destroyer of _!", + "pick": 2 + }, + { + "text": "It lurks in the night. It hungers for flesh. This summer, no one is safe from _.", + "pick": 1 + }, + { + "text": "If you can't handle _, you'd better stay away from _.", + "pick": 2 + }, + { + "text": "This is the prime of my life. I'm young, hot, and full of _.", + "pick": 1 + }, + { + "text": "I'm pretty sure I'm high right now, because I'm absolutely mesmerized by _.", + "pick": 1 + }, + { + "text": "This year's hottest album is \"_\" by _.", + "pick": 2 + }, + { + "text": "Every step towards _ gets me a little closer to _.", + "pick": 2 + }, + { + "text": "Forget everything you know about _, because now we've supercharged it with _!", + "pick": 2 + }, + { + "text": "Honey, I have a new role-play I want to try tonight! You can be _, and I'll be _.", + "pick": 2 + }, + { + "text": "Do the Dew ® with our most extreme flavor yet! Get ready for Mountain Dew _!", + "pick": 1 + }, + { + "text": "Armani suit: $1,000. Dinner for two at that swanky restaurant: $300. The look on her face when you surprise her with _: priceless.", + "pick": 1 + }, + { + "text": "In his new action comedy, Jackie Chan must fend off ninjas while also dealing with _.", + "pick": 1 + }, + { + "text": "Well what do you have to say for yourself, Casey? This is the third time you've been sent to the principal's office for _.", + "pick": 1 + }, + { + "text": "Here at the Academy for Gifted Children, we allow students to explore _ at their own pace.", + "pick": 1 + }, + { + "text": "Heed my voice, mortals! I am the god of _, and I will not tolerate _!", + "pick": 2 + }, + { + "text": "I don't mean to brag, but they call me the Michael Jordan of _.", + "pick": 1 + }, + { + "text": "Why am I broke?", + "pick": 1 + }, + { + "text": "Help me doctor, I've got _ in my butt!", + "pick": 1 + }, + { + "text": "Hi MTV! My name is Kendra, I live in Malibu, I'm into _, and I love to have a good time.", + "pick": 1 + }, + { + "text": "Patient presents with _. Likely a result of _.", + "pick": 2 + }, + { + "text": "Life's pretty tough in the fast lane. That's why I never leave the house without _.", + "pick": 1 + }, + { + "text": "What's making things awkward in the sauna?", + "pick": 1 + }, + { + "text": "Get ready for the movie of the summer! One cop plays by the book. The other's only interested in one thing: _.", + "pick": 1 + }, + { + "text": "Having the worst day EVER. #_", + "pick": 1 + }, + { + "text": "In his farewell address, George Washington famously warned Americans about the dangers of _.", + "pick": 1 + }, + { + "text": "Don't forget! Beginning this week, Casual Friday will officially become \"_ Friday.\"", + "pick": 1 + }, + { + "text": "What killed my boner?", + "pick": 1 + }, + { + "text": "Yo' mama so fat she _!", + "pick": 1 + }, + { + "text": "Well if _ is good enough for _, it's good enough for me.", + "pick": 2 + }, + { + "text": "Hi, this is Jim from accounting. We noticed a $1,200 charge labeled \"_\". Can you explain?", + "pick": 1 + }, + { + "text": "Do you lack energy? Does it sometimes feel like the whole world is _? Zoloft.®", + "pick": 1 + }, + { + "text": "WHOOO! God damn I love _!", + "pick": 1 + }, + { + "text": "Now in bookstores: \"The Audacity of _\", by Barack Obama.", + "pick": 1 + }, + { + "text": "And today's soup is Cream of _.", + "pick": 1 + }, + { + "pick": 1, + "text": "I work my ass off all day for this family, and this is what I come home to? _!?" + }, + { + "pick": 1, + "text": "I have a strict policy. First date, dinner. Second date, kiss. Third date, _." + }, + { + "pick": 1, + "text": "When I was a kid, we used to play Cowboys and _." + }, + { + "pick": 1, + "text": "This is America. If you don't work hard, you don't succeed. I don't care if you're black, white, purple, or _." + }, + { + "pick": 1, + "text": "You Won't Believe These 15 Hilarious _ Bloopers!" + }, + { + "pick": 1, + "text": "James is a lonely boy. But when he discovers a secret door in his attic, he meets a magical new friend: _." + }, + { + "pick": 1, + "text": "Don't worry kid. It gets better. I've been living with _ for 20 years." + }, + { + "pick": 1, + "text": "My grandfather worked his way up from nothing. When he came to this country, all he had was the shoes on his feet and _." + }, + { + "pick": 1, + "text": "Behind every powerful man is _." + }, + { + "pick": 1, + "text": "You are not alone. Millions of Americans struggle with _ every day." + }, + { + "pick": 1, + "text": "Come to Dubai, where you can relax in our world famous spas, experience the nightlife, or simply enjoy _ by the poolside." + }, + { + "pick": 1, + "text": "\"This is madness.\" \"No, THIS IS _!\"" + }, + { + "pick": 1, + "text": "Listen Gary, I like you. But if you want that corner office, you're going to have to show me _." + }, + { + "pick": 1, + "text": "I went to the desert and ate of the peyote cactus. Turns out my spirit animal is _." + }, + { + "pick": 1, + "text": "And would you like those buffalo wings mild, hot, or _?" + }, + { + "pick": 1, + "text": "The six things I could never do without: oxygen, Facebook, chocolate, Netflix, friends, and _ LOL!" + }, + { + "pick": 1, + "text": "Why won't you make love to me anymore? Is it _?" + }, + { + "pick": 1, + "text": "Puberty is a time of change. You might notice hair growing in new places. You might develop an interest in _. This is normal." + }, + { + "pick": 1, + "text": "I'm sorry, Mrs. Chen, but there was nothing we could do. At 4:15 this morning, your son succumbed to _." + }, + { + "pick": 1, + "text": "I'm Miss Tennessee, and if I could make the world better by changing one thing, I would get rid of _." + }, + { + "pick": 1, + "text": "Tonight we will have sex. And afterwards, If you'd like, a little bit of _." + }, + { + "pick": 1, + "text": "Everybody join hands and close your eyes. Do you sense that? That's the presence of _ in this room." + }, + { + "pick": 1, + "text": "To become a true Yanomamo warrior, you must prove that you can withstand _ without crying out." + }, + { + "pick": 1, + "text": "Y'all ready to get this thing started? I'm Nick Cannon, and this is America's Got _." + }, + { + "pick": 1, + "text": "If you had to describe the Card Czar, using only one of the cards in your hand, which one would it be?" + }, + { + "pick": 2, + "text": "_ be all like _." + }, + { + "pick": 1, + "text": "Art isn't just a painting in a stuffy museum. Art is alive. Art is _." + }, + { + "pick": 1, + "text": "As reparations for slavery, all African Americans will receive _." + }, + { + "pick": 1, + "text": "As Teddy Roosevelt said, the four manly virtues are honor, temperance, industry, and _." + }, + { + "pick": 1, + "text": "Best you go back where you came from, now. We don't take too kindly to _ in these parts." + }, + { + "pick": 1, + "text": "CNN breaking news! Scientists discover _." + }, + { + "pick": 1, + "text": "Coming to Red Lobster® this month, _." + }, + { + "pick": 1, + "text": "Congratulations! You have been selected for our summer internship program. While we are unable to offer a salary, we can offer you _." + }, + { + "pick": 1, + "text": "Dance like there's nobody watching, love like you'll never be hurt, and live like you're _." + }, + { + "pick": 1, + "text": "Errbody in the club _." + }, + { + "pick": 1, + "text": "Feeling so grateful! #amazing #mylife #_." + }, + { + "pick": 1, + "text": "Girls just wanna have _." + }, + { + "pick": 1, + "text": "Google Calendar alert: _ in 10 minutes." + }, + { + "pick": 1, + "text": "I don't believe in God. I believe in _." + }, + { + "pick": 1, + "text": "I got rhythm, I've got music, I've got _. Who could ask for anything more?" + }, + { + "pick": 1, + "text": "I may not be much to look at, but I fuck like _." + }, + { + "pick": 1, + "text": "I tell you, it was a non-stop fuckfest. When it was over, my asshole looked like _." + }, + { + "pick": 1, + "text": "I'll take the BBQ bacon burger with friend egg and fuck it how about _." + }, + { + "pick": 1, + "text": "I'm sorry, sir, but your insurance plan doesn't cover injuries caused by _." + }, + { + "pick": 1, + "text": "I've had a horrible vision, father. I saw mountains crumbling, stars falling from the sky. I saw _." + }, + { + "pick": 1, + "text": "If at first you don't succeed, try _." + }, + { + "pick": 1, + "text": "In the 1950s, psychologists prescribed _ as a cure for homosexually." + }, + { + "pick": 1, + "text": "LSD + _ = really bad time." + }, + { + "pick": 1, + "text": "\"Mom's to-do list:
- Buy Groceries.
- Clean up _.
- Soccer Practice.\"" + }, + { + "pick": 1, + "text": "Most Americans would not vote for a candidate who is openly _." + }, + { + "pick": 1, + "text": "No, no, no, no, no, no, NO! I will NOT let _ ruin this wedding." + }, + { + "pick": 1, + "text": "Oh no! Siri, how do I fix _?" + }, + { + "pick": 1, + "text": "One more thing. Watch out for Big Mike. They say he killed a man with _." + }, + { + "pick": 1, + "text": "Ooo, daddy like _." + }, + { + "pick": 1, + "text": "Poor Brandon, still living in his parent's basement. I heard he never got over _." + }, + { + "pick": 1, + "text": "Run, run, as fast as you can! You can't catch me, I'm _!" + }, + { + "pick": 1, + "text": "She's a lady in the streets, _ in the sheets." + }, + { + "pick": 1, + "text": "She's just one of the guys, you know? She likes beer, and football, and _." + }, + { + "pick": 1, + "text": "Son, take it from someone who's been around the block a few times. Nothin' puts her in the mood like _." + }, + { + "pick": 1, + "text": "Summer lovin', had me a blast. _, happened so fast." + }, + { + "pick": 1, + "text": "\"The top Google auto-complete results for \"Barack Obama\":
- Barack Obama Height.
- Barack Obama net worth.
- Barack Obama _.\"" + }, + { + "pick": 1, + "text": "Then the princess kissed the frog, and all of a sudden the frog was _!" + }, + { + "pick": 1, + "text": "There is no God. It's just _ and then you die." + }, + { + "pick": 1, + "text": "This Friday at the Liquid Lunge, it's _ Night! Ladies drink free." + }, + { + "pick": 1, + "text": "We do not shake with our left hands in this country. That is the hand we use for _." + }, + { + "pick": 1, + "text": "Well if _ is a crime, then lock me up!" + }, + { + "pick": 1, + "text": "Well, shit. My eyes ain't so good, but I'll eat my own boot if that ain't _!" + }, + { + "pick": 1, + "text": "What are all those whales singing about?" + }, + { + "pick": 1, + "text": "What sucks balls?" + }, + { + "pick": 1, + "text": "What totally destroyed my asshole?" + }, + { + "pick": 1, + "text": "What turned me into a Republican?" + }, + { + "pick": 1, + "text": "What will end racism once and for all?" + }, + { + "pick": 1, + "text": "What's a total waste of Hillary Clinton's time?" + }, + { + "pick": 1, + "text": "What's about to take dance floor to the next level?" + }, + { + "pick": 1, + "text": "What's the gayest?" + }, + { + "pick": 1, + "text": "What's the most problematic?" + }, + { + "pick": 1, + "text": "Why am I laughing and crying and taking off my clothes?" + }, + { + "pick": 1, + "text": "With a one-time gift of just $10, you can save this child from _." + }, + { + "pick": 1, + "text": "You know who else liked _? Hitler." + }, + { + "pick": 1, + "text": "You won't believe what's in my pussy. It's _." + }, + { + "text": "Siskel and Ebert have panned _ as \"poorly conceived\" and \"sloppily executed.\"", + "pick": 1 + }, + { + "text": "Up next on Nickelodeon: \"Clarissa Explains _.\"", + "pick": 1 + }, + { + "text": "Believe it or not, Jim Carrey can do a dead-on impression of _.", + "pick": 1 + }, + { + "text": "It's Morphin' Time! Mastadon! Pterodactyl! Triceratops! Sabertooth Tiger! _!", + "pick": 1 + }, + { + "text": "I'm a bitch, I'm a lover, I'm a child, I'm _.", + "pick": 1 + }, + { + "text": "How did Stella get her groove back?", + "pick": 1 + }, + { + "text": "Tonight on SNICK: \"Are You Afraid of _?\"", + "pick": 1 + }, + { + "pick": 1, + "text": "And in the end, the dragon was not evil; he just wanted _." + }, + { + "pick": 2, + "text": "Critics are raving about HBO's new Game of Thrones spin-off, \"_ of _.\"" + }, + { + "pick": 1, + "text": "Having tired of poetry and music, the immortal elves now fill their days with _." + }, + { + "pick": 1, + "text": "Legend tells of a princess who has been asleep for a thousand years and can only be awoken by _." + }, + { + "pick": 1, + "text": "Who blasphemes and bubbles at the center of all infinity, whose name no lips dare speak aloud, and who gnaws hungrily in inconceivable, unlighted chambers beyond time?" + }, + { + "pick": 1, + "text": "Your father was a powerful wizard, Harry. Before he died, he left you something very precious: _." + }, + { + "pick": 1, + "text": "I'm Bobby Flay, and if you can't stand _, get out of the kitchen!" + }, + { + "pick": 1, + "text": "It's not delivery. It's _." + }, + { + "pick": 1, + "text": "Aw babe, your burps smell like _!" + }, + { + "pick": 1, + "text": "Don't miss Rachel Ray's hit new show, Cooking with _." + }, + { + "pick": 1, + "text": "Excuse me, waiter. Could take this back? This soup tastes like _." + }, + { + "pick": 1, + "text": "Now on Netflix: Jiro Dreams of _." + }, + { + "pick": 2, + "text": "In line with our predictions, we find a robust correlation between _ and _ (p>.05)." + }, + { + "pick": 1, + "text": "In what's being hailed as a major breakthrough, scientists have synthesized _ in the lab." + }, + { + "pick": 1, + "text": "A study published in Nature this week found that _ is good for you in small doses." + }, + { + "pick": 2, + "text": "In an attempt to recreate conditions just after the Big Bang, physicists at the LHC are observing collisions between _ and _." + }, + { + "pick": 1, + "text": "What really killed the dinosaurs?" + }, + { + "pick": 1, + "text": "Hey there, Young Scientists! Put on your labcoats and strap on your safety goggles, because today we're learning about _!" + }, + { + "pick": 2, + "text": "Today on MythBusters, we found out how long _ can withstand _." + } + ], + "whiteCards": [ + "Coat hanger abortions.", + "Man meat.", + "Autocannibalism.", + "Vigorous jazz hands.", + "Flightless birds.", + "Pictures of boobs.", + "Doing the right thing.", + "The violation of our most basic human rights.", + "Viagra®.", + "Self-loathing.", + "Spectacular abs.", + "A balanced breakfast.", + "Roofies.", + "Concealing a boner.", + "Amputees.", + "The Big Bang.", + "Former President George W. Bush.", + "The Rev. Dr. Martin Luther King, Jr.", + "Smegma.", + "Being marginalized.", + "Cuddling.", + "Laying an egg.", + "The Pope.", + "Aaron Burr.", + "Genital piercings.", + "Fingering.", + "A bleached asshole.", + "Horse meat.", + "Fear itself.", + "Science.", + "Elderly Japanese men.", + "Stranger danger.", + "The terrorists.", + "Praying the gay away.", + "Same-sex ice dancing.", + "Ethnic cleansing.", + "Cheating in the Special Olympics.", + "German dungeon porn.", + "Bingeing and purging.", + "Making a pouty face.", + "William Shatner.", + "Heteronormativity.", + "Nickelback.", + "Tom Cruise.", + "The profoundly handicapped.", + "The placenta.", + "Chainsaws for hands.", + "Arnold Schwarzenegger.", + "An icepick lobotomy.", + "Goblins.", + "Object permanence.", + "Dying.", + "Foreskin.", + "A falcon with a cap on its head.", + "Hormone injections.", + "Dying of dysentery.", + "Sexy pillow fights.", + "The invisible hand.", + "A really cool hat.", + "Sean Penn.", + "Heartwarming orphans.", + "The clitoris.", + "The Three-Fifths compromise.", + "A sad handjob.", + "Men.", + "Historically black colleges.", + "A micropenis.", + "Raptor attacks.", + "Agriculture.", + "Vikings.", + "Pretending to care.", + "The Underground Railroad.", + "My humps.", + "Being a dick to children.", + "Geese.", + "Bling.", + "Sniffing glue.", + "The South.", + "An Oedipus complex.", + "Eating all of the cookies before the AIDS bake-sale.", + "Sexting.", + "YOU MUST CONSTRUCT ADDITIONAL PYLONS.", + "Mutually-assured destruction.", + "Sunshine and rainbows.", + "Count Chocula.", + "Sharing needles.", + "Being rich.", + "Skeletor.", + "A sausage festival.", + "Michael Jackson.", + "Emotions.", + "Farting and walking away.", + "The Chinese gymnastics team.", + "Necrophilia.", + "Spontaneous human combustion.", + "Yeast.", + "Leaving an awkward voicemail.", + "Dick Cheney.", + "White people.", + "Penis envy.", + "Teaching a robot to love.", + "Sperm whales.", + "Scrubbing under the folds.", + "Panda sex.", + "Whipping it out.", + "Catapults.", + "Masturbation.", + "Natural selection.", + "Opposable thumbs.", + "A sassy black woman.", + "AIDS.", + "The KKK.", + "Figgy pudding.", + "Seppuku.", + "Gandhi.", + "Preteens.", + "Toni Morrison's vagina.", + "Five-Dollar Footlongs™.", + "Land mines.", + "A sea of troubles.", + "A zesty breakfast burrito.", + "Christopher Walken.", + "Friction.", + "Balls.", + "Dental dams.", + "A can of whoop-ass.", + "A tiny horse.", + "Waiting 'til marriage.", + "Authentic Mexican cuisine.", + "Genghis Khan.", + "Old-people smell.", + "Feeding Rosie O'Donnell.", + "Pixelated bukkake.", + "Friends with benefits.", + "The token minority.", + "The Tempur-Pedic® Swedish Sleep System™.", + "A thermonuclear detonation.", + "Take-backsies.", + "The Rapture.", + "A cooler full of organs.", + "Sweet, sweet vengeance.", + "RoboCop.", + "Keanu Reeves.", + "Drinking alone.", + "Giving 110%.", + "Flesh-eating bacteria.", + "The American Dream.", + "Taking off your shirt.", + "Me time.", + "A murder most foul.", + "The inevitable heat death of the universe.", + "The folly of man.", + "That thing that electrocutes your abs.", + "Cards Against Humanity.", + "Fiery poops.", + "Poor people.", + "Edible underpants.", + "Britney Spears at 55.", + "All-you-can-eat shrimp for $4.99.", + "Pooping back and forth. Forever.", + "Fancy Feast®.", + "Jewish fraternities.", + "Being a motherfucking sorcerer.", + "Pulling out.", + "Picking up girls at the abortion clinic.", + "The homosexual agenda.", + "The Holy Bible.", + "Passive-agression.", + "Ronald Reagan.", + "Vehicular manslaughter.", + "Nipple blades.", + "Assless chaps.", + "Full frontal nudity.", + "Hulk Hogan.", + "Daddy issues.", + "The hardworking Mexican.", + "Natalie Portman.", + "Waking up half-naked in a Denny's parking lot.", + "God.", + "Sean Connery.", + "Saxophone solos.", + "Gloryholes.", + "The World of Warcraft.", + "Homeless people.", + "Scalping.", + "Darth Vader.", + "Eating the last known bison.", + "Guys who don't call.", + "Hot Pockets®.", + "A time travel paradox.", + "The milk man.", + "Testicular torsion.", + "Dropping a chandelier on your enemies and riding the rope up.", + "World peace.", + "A salty surprise.", + "Poorly-timed Holocaust jokes.", + "Smallpox blankets.", + "Licking things to claim them as your own.", + "The heart of a child.", + "Robert Downey, Jr.", + "Lockjaw.", + "Eugenics.", + "A good sniff.", + "Friendly fire.", + "The taint; the grundle; the fleshy fun-bridge.", + "Wearing underwear inside-out to avoid doing laundry.", + "Hurricane Katrina.", + "Free samples.", + "Jerking off into a pool of children's tears.", + "A foul mouth.", + "The glass ceiling.", + "Republicans.", + "Explosions.", + "Michelle Obama's arms.", + "Getting really high.", + "Attitude.", + "Sarah Palin.", + "The Übermensch.", + "Altar boys.", + "My soul.", + "My sex life.", + "Pedophiles.", + "72 virgins.", + "Pabst Blue Ribbon.", + "Domino's™ Oreo™ Dessert Pizza.", + "A snapping turtle biting the tip of your penis.", + "The Blood of Christ.", + "Half-assed foreplay.", + "My collection of high-tech sex toys.", + "A middle-aged man on roller skates.", + "Bitches.", + "Bill Nye the Science Guy.", + "Italians.", + "A windmill full of corpses.", + "Adderall™.", + "Crippling debt.", + "A stray pube.", + "Prancing.", + "Passing a kidney stone.", + "A brain tumor.", + "Leprosy.", + "Puppies!", + "Bees?", + "Frolicking.", + "Repression.", + "Road head.", + "A bag of magic beans.", + "An asymmetric boob job.", + "Dead parents.", + "Public ridicule.", + "A mating display.", + "A mime having a stroke.", + "Stephen Hawking talking dirty.", + "African children.", + "Mouth herpes.", + "Overcompensation.", + "Riding off into the sunset.", + "Being on fire.", + "Tangled Slinkys.", + "Civilian casualties.", + "Auschwitz.", + "My genitals.", + "Not reciprocating oral sex.", + "Lactation.", + "Being fabulous.", + "Shaquille O'Neal's acting career.", + "My relationship status.", + "Asians who aren't good at math.", + "Alcoholism.", + "Incest.", + "Grave robbing.", + "Hope.", + "8 oz. of sweet Mexican black-tar heroin.", + "Kids with ass cancer.", + "Winking at old people.", + "The Jews.", + "Justin Bieber.", + "Doin' it in the butt.", + "A lifetime of sadness.", + "The Hamburglar.", + "Swooping.", + "Classist undertones.", + "New Age music.", + "Not giving a shit about the Third World.", + "The Kool-Aid Man.", + "A hot mess.", + "Tentacle porn.", + "Lumberjack fantasies.", + "The gays.", + "Scientology.", + "Estrogen.", + "GoGurt®.", + "Judge Judy.", + "Dick fingers.", + "Racism.", + "Surprise sex!", + "Police brutality.", + "Passable transvestites.", + "The Virginia Tech Massacre.", + "When you fart and a little bit comes out.", + "Oompa-Loompas.", + "A fetus.", + "Obesity.", + "Tasteful sideboob.", + "Hot people.", + "BATMAN!!!", + "Black people.", + "A gassy antelope.", + "Sexual tension.", + "Third base.", + "Racially-biased SAT questions.", + "Porn stars.", + "A Super Soaker™ full of cat pee.", + "Muhammed (Praise Be Unto Him).", + "Puberty.", + "A disappointing birthday party.", + "An erection that lasts longer than four hours.", + "White privilege.", + "Getting so angry that you pop a boner.", + "Wifely duties.", + "Two midgets shitting into a bucket.", + "Queefing.", + "Wiping her butt.", + "Golden showers.", + "Barack Obama.", + "Nazis.", + "A robust mongoloid.", + "An M. Night Shyamalan plot twist.", + "Getting drunk on mouthwash.", + "Lunchables™.", + "Women in yogurt commercials.", + "John Wilkes Booth.", + "Powerful thighs.", + "Mr. Clean, right behind you.", + "Multiple stab wounds.", + "Cybernetic enhancements.", + "Serfdom.", + "Kanye West.", + "Women's suffrage.", + "Children on leashes.", + "Harry Potter erotica.", + "The Dance of the Sugar Plum Fairy.", + "Lance Armstrong's missing testicle.", + "Parting the Red Sea.", + "The Amish.", + "Dead babies.", + "Child beauty pageants.", + "AXE Body Spray.", + "Centaurs.", + "Copping a feel.", + "Grandma.", + "Famine.", + "The Trail of Tears.", + "The miracle of childbirth.", + "Finger painting.", + "A monkey smoking a cigar.", + "The Make-A-Wish® Foundation.", + "Anal beads.", + "The Force.", + "Kamikaze pilots.", + "Dry heaving.", + "Active listening.", + "Ghosts.", + "The Hustle.", + "Peeing a little bit.", + "Another goddamn vampire movie.", + "Shapeshifters.", + "The Care Bear Stare.", + "Hot cheese.", + "A mopey zoo lion.", + "A defective condom.", + "Teenage pregnancy.", + "A Bop It™.", + "Expecting a burp and vomiting on the floor.", + "Horrifying laser hair removal accidents.", + "Boogers.", + "Unfathomable stupidity.", + "Breaking out into song and dance.", + "Soup that is too hot.", + "Morgan Freeman's voice.", + "Getting naked and watching Nickelodeon.", + "MechaHitler.", + "Flying sex snakes.", + "The true meaning of Christmas.", + "My inner demons.", + "Pac-Man uncontrollably guzzling cum.", + "My vagina.", + "A homoerotic volleyball montage.", + "Actually taking candy from a baby.", + "Crystal meth.", + "Exactly what you'd expect.", + "Natural male enhancement.", + "Passive-aggressive Post-it notes.", + "Inappropriate yodeling.", + "Lady Gaga.", + "The Little Engine That Could.", + "Vigilante justice.", + "A death ray.", + "Poor life choices.", + "A gentle caress of the inner thigh.", + "Embryonic stem cells.", + "Nicolas Cage.", + "Firing a rifle into the air while balls deep in a squealing hog.", + "Switching to Geico®.", + "The chronic.", + "Erectile dysfunction.", + "Home video of Oprah sobbing into a Lean Cuisine®.", + "A bucket of fish heads.", + "50,000 volts straight to the nipples.", + "Being fat and stupid.", + "Hospice care.", + "A pyramid of severed heads.", + "Getting married, having a few kids, buying some stuff, retiring to Florida, and dying.", + "A subscription to Men's Fitness.", + "Crucifixion.", + "A micropig wearing a tiny raincoat and booties.", + "Some god-damn peace and quiet.", + "Used panties.", + "A tribe of warrior women.", + "The penny whistle solo from \"My Heart Will Go On.\"", + "An oversized lollipop.", + "Helplessly giggling at the mention of Hutus and Tutsis.", + "Not wearing pants.", + "Consensual sex.", + "Her Majesty, Queen Elizabeth II.", + "Funky fresh rhymes.", + "The art of seduction.", + "The Devil himself.", + "Advice from a wise, old black man.", + "Destroying the evidence.", + "The light of a billion suns.", + "Wet dreams.", + "Synergistic management solutions.", + "Growing a pair.", + "Silence.", + "An M16 assault rifle.", + "Poopy diapers.", + "A live studio audience.", + "The Great Depression.", + "A spastic nerd.", + "Rush Limbaugh's soft, shitty body.", + "Tickling Sean Hannity, even after he tells you to stop.", + "Stalin.", + "Brown people.", + "Rehab.", + "Capturing Newt Gingrich and forcing him to dance in a monkey suit.", + "Battlefield amputations.", + "An uppercut.", + "Shiny objects.", + "An ugly face.", + "Menstrual rage.", + "A bitch slap.", + "One trillion dollars.", + "Chunks of dead prostitute.", + "The entire Mormon Tabernacle Choir.", + "The female orgasm.", + "Extremely tight pants.", + "The Boy Scouts of America.", + "Stormtroopers.", + "Throwing a virgin into a volcano.", + "Getting in her pants, politely.", + "Gladiatorial combat.", + "Good grammar.", + "Hipsters.", + "Gandalf.", + "Genetically engineered super-soldiers.", + "George Clooney's musk.", + "Getting abducted by Peter Pan.", + "Eating an albino.", + "Enormous Scandinavian women.", + "Fabricating statistics.", + "Finding a skeleton.", + "Suicidal thoughts.", + "Dancing with a broom.", + "Deflowering the princess.", + "Dorito breath.", + "One thousand Slim Jims.", + "My machete.", + "Overpowering your father.", + "Ominous background music.", + "Media coverage.", + "Making the penises kiss.", + "Moral ambiguity.", + "Medieval Times® Dinner & Tournament.", + "Mad hacky-sack skills.", + "Just the tip.", + "Literally eating shit.", + "Leveling up.", + "Insatiable bloodlust.", + "Historical revisionism.", + "Jean-Claude Van Damme.", + "Jafar.", + "The boners of the elderly.", + "The economy.", + "Statistically validated stereotypes.", + "Sudden Poop Explosion Disease.", + "Slow motion.", + "Space muffins.", + "Sexual humiliation.", + "Sexy Siamese twins.", + "Santa Claus.", + "Scrotum tickling.", + "Ripping into a man's chest and pulling out his still-beating heart.", + "Ryan Gosling riding in on a white horse.", + "Quivering jowls.", + "Revenge fucking.", + "Pistol-whipping a hostage.", + "Quiche.", + "Zeus's sexual appetites.", + "Words, words, words.", + "Tripping balls.", + "Being a busy adult with many important things to do.", + "The four arms of Vishnu.", + "The shambling corpse of Larry King.", + "The hiccups.", + "The harsh light of day.", + "The Gulags.", + "The Fanta® girls.", + "A big black dick.", + "A beached whale.", + "A low standard of living.", + "A nuanced critique.", + "A bloody pacifier.", + "A crappy little hand.", + "Shaft.", + "Being a dinosaur.", + "Beating your wives.", + "Neil Patrick Harris.", + "Coughing into a vagina.", + "Carnies.", + "Nubile slave boys.", + "Bosnian chicken farmers.", + "A web of lies.", + "A rival dojo.", + "A passionate Latino lover.", + "Panty raids.", + "Appreciative snapping.", + "Apologizing.", + "Clams.", + "A woman scorned.", + "Being awesome at sex.", + "Spring break!", + "Another shot of morphine.", + "Dining with cardboard cutouts of the cast of \"Friends.\"", + "A soulful rendition of \"Ol' Man River.\"", + "Making a friend.", + "A sweaty, panting leather daddy.", + "Intimacy problems.", + "The new Radiohead album.", + "Pretty Pretty Princess Dress-Up Board Game®.", + "A man in yoga pants with a ponytail and feather earrings.", + "An army of skeletons.", + "A squadron of moles wearing aviator goggles.", + "Beefin' over turf.", + "The Google.", + "Bullshit.", + "A sweet spaceship.", + "A 55-gallon drum of lube.", + "Special musical guest, Cher.", + "The human body.", + "Mild autism.", + "Nunchuck moves.", + "Whipping a disobedient slave.", + "An ether-soaked rag.", + "Oncoming traffic.", + "A dollop of sour cream.", + "A slightly shittier parallel universe.", + "My first kill.", + "Boris the Soviet Love Hammer.", + "The grey nutrient broth that sustains Mitt Romney.", + "Tiny nipples.", + "Power.", + "Death by Steven Seagal.", + "A Burmese tiger pit.", + "Basic human decency.", + "Grandpa's ashes.", + "One Ring to rule them all.", + "The day the birds attacked.", + "Fetal alcohol syndrome.", + "Graphic violence, adult language, and some sexual content.", + "A bigger, blacker dick.", + "The mere concept of Applebee's®.", + "A sad fat dragon with no friends.", + "A piñata full of scorpions.", + "Existing.", + "Hillary Clinton's death stare.", + "Catastrophic urethral trauma.", + "Double penetration.", + "Daddy's belt.", + "Swiftly achieving orgasm.", + "Mooing.", + "Rising from the grave.", + "Subduing a grizzly bear and making her your wife.", + "Some really fucked-up shit.", + "Weapons-grade plutonium.", + "All of this blood.", + "Scrotal frostbite.", + "Taking a man's eyes and balls out and putting his eyes where his balls go and then his balls in the eye holes.", + "The mixing of the races.", + "Pumping out a baby every nine months.", + "Tongue.", + "Loki, the trickster god.", + "Whining like a little bitch.", + "Wearing an octopus for a hat.", + "An unhinged ferris wheel rolling toward the sea.", + "Finding Waldo.", + "Upgrading homeless people to mobile hotspots.", + "A magic hippie love cloud.", + "Fuck Mountain.", + "Living in a trashcan.", + "The corporations.", + "Getting hilariously gang-banged by the Blue Man Group.", + "Jeff Goldblum.", + "Survivor's guilt.", + "Me.", + "All my friends dying.", + "Shutting the fuck up.", + "An ass disaster.", + "Some kind of bird-man.", + "The entire Internet.", + "Going around punching people.", + "A boo-boo.", + "Indescribable loneliness.", + "Having sex on top of a pizza.", + "Chugging a lava lamp.", + "Warm, velvety muppet sex.", + "Running naked through a mall, pissing and shitting everywhere.", + "Nothing.", + "Samuel L. Jackson.", + "Self-flagellation.", + "The systematic destruction of an entire people and their way of life.", + "The Quesadilla Explosion Salad™ from Chili's®.", + "Reverse cowgirl.", + "Vietnam flashbacks.", + "Actually getting shot, for real.", + "Not having sex.", + "Cock.", + "Dying alone and in pain.", + "A cop who is also a dog.", + "The way white people is.", + "Gay aliens.", + "The primal, ball-slapping sex your parents are having right now.", + "A cat video so cute that your eyes roll back and your spine slides out of your anus.", + "A lamprey swimming up the toilet and latching onto your taint.", + "Slapping a racist old lady.", + "A black male in his early 20s, last seen wearing a hoodie.", + "Jumping out at people.", + "Three months in the hole.", + "Blood farts.", + "The Land of Chocolate.", + "A botched circumcision.", + "My manservant, Claude.", + "Vomiting mid-blowjob.", + "Letting everyone down.", + "Having shotguns for legs.", + "Bill Clinton, naked on a bearskin rug with a saxophone.", + "Mufasa's death scene.", + "The Harlem Globetrotters.", + "Demonic possession.", + "Fisting.", + "The thin veneer of situational causality that underlies porn.", + "Girls that always be textin'.", + "Blowing some dudes in an alley.", + "A spontaneous conga line.", + "A vagina that leads to another dimension.", + "Disco fever.", + "Getting your dick stuck in a Chinese finger trap with another dick.", + "Drinking ten 5-hour ENERGYs® to get fifty continuous hours of energy.", + "Sneezing, farting, and coming at the same time.", + "Some douche with an acoustic guitar.", + "Spending lots of money.", + "Putting an entire peanut butter and jelly sandwich into the VCR.", + "An unstoppable wave of fire ants.", + "A greased-up Matthew McConaughey.", + "Flying robots that kill people.", + "Unlimited soup, salad, and breadsticks.", + "Crying into the pages of Sylvia Plath.", + "The moist, demanding chasm of his mouth.", + "Filling every orifice with butterscotch pudding.", + "An all-midget production of Shakespeare's Richard III.", + "Screaming like a maniac.", + "Not contributing to society in any meaningful way.", + "A pile of squirming bodies.", + "Buying the right pants to be cool.", + "Roland the Farter, flatulist to the king.", + "That ass.", + "A surprising amount of hair.", + "Eating Tom Selleck's mustache to gain his powers.", + "Velcro™.", + "A PowerPoint presentation.", + "Crazy opium eyes.", + "10 Incredible Facts About the Anus.", + "An interracial handshake.", + "Moderate-to-severe joint pain.", + "Finally finishing off the Indians.", + "Sugar madness.", + "Actual mutants with medical conditions and no superpowers.", + "The secret formula for ultimate female satisfaction.", + "The complex geopolitical quagmire that is the Middle East.", + "Fucking a corpse back to life.", + "Neil Diamond's Greatest Hits.", + "Calculating every mannerism so as not to suggest homosexuality.", + "Whatever a McRib® is made of.", + "No clothes on, penis in vagina.", + "All the single ladies.", + "Whispering all sexy.", + "How awesome I am.", + "Ass to mouth.", + "Smoking crack, for instance.", + "Falling into the toilet.", + "A dance move that's just sex.", + "The size of my penis.", + "Some sort of Asian.", + "A hopeless amount of spiders.", + "Party Mexicans.", + "Drinking responsibly.", + "The safe word.", + "Angelheaded hipsters burning for the ancient heavenly connection to the starry dynamo in the machinery of night.", + "Bouncing up and down.", + "Jizz.", + "Ambiguous sarcasm.", + "A shiny rock that proves I love you.", + "Dem titties.", + "My worthless son.", + "Exploding pigeons.", + "A Ugandan warlord.", + "My sex dungeon.", + "A kiss on the lips.", + "Child Protective Services.", + "A Native American who solves crimes by going into the spirit world.", + "Doo-doo.", + "The peaceful and nonthreatening rise of China.", + "Sports.", + "A fart.", + "Unquestioning obedience.", + "Three consecutive seconds of happiness.", + "Grammar nazis who are also regular Nazis.", + "Snorting coke off a clown's boner.", + "Africa.", + "Depression.", + "A horse with no legs.", + "The euphoric rush of strangling a drifter.", + "Khakis.", + "Interspecies marriage.", + "A gender identity that can only be conveyed through slam poetry.", + "Almost giving money to a homeless person.", + "Stuff a child's face with Fun Dip® until he starts having fun.", + "What Jesus would do.", + "A for-real lizard that spits blood from its eyes.", + "Blackula.", + "The tiniest shred of evidence that God is real.", + "My dad's dumb fucking face.", + "Prince Ali,
fabulous he,
Ali Ababwa.", + "A manhole.", + "A sex goblin with a carnival penis.", + "A bunch of idiots playing a card game instead of interacting like normal humans.", + "A sex comet from Neptune that plunges the Earth into eternal sexiness.", + "Sharks with legs.", + "Injecting speed into one arm and horse tranquilizer into the other.", + "Lots and lots of abortions.", + "Seeing things from Hitler's perspective", + "Too much cocaine.", + "Doing the right stuff to her nipples.", + "Giant sperm from outer space.", + "Oil!", + "Ennui.", + "A powered exoskeleton.", + "A disappointing salad.", + "Mom's new boyfriend.", + "Unrelenting genital punishment.", + "Denzel.", + "The swim team, all at once.", + "The eight gay warlocks who dictate the rules of fashion.", + "Being nine years old.", + "The unbelievable world of mushrooms.", + "The Abercrombie & Fitch lifestyle.", + "Vegetarian options.", + "My first period.", + "Having been dead for a while.", + "Backwards knees.", + "Being paralyzed from the neck down.", + "Seeing my village burned and my family slaughtered before my eyes.", + "A zero-risk way to make $2,000 from home.", + "A crazy little thing called love.", + "Ancient Athenian boy-fucking", + "Out-of-this-world bazongas.", + "The ghost of Marlon Brando.", + "The basic suffering that pervades all of existence.", + "Being worshipped as the one true God.", + "Figuring out how to have sex with a dolphin.", + "All these decorative pillows.", + "A mouthful of potato salad.", + "Russian super-tuberculosis.", + "A reason not to commit suicide.", + "Going to a high school reunion on ketamine.", + "The passage of time.", + "Child support payments.", + "Changing a person's mind with logic and facts.", + "My boyfriend's stupid penis.", + "The tiger that killed my father.", + "Genghis Khan's DNA.", + "Boring vaginal sex.", + "40 acres and a mule.", + "A whole new kind of porn.", + "Slowly easing down onto a cucumber.", + "Wearing glasses and sounding smart.", + "AIDS monkeys.", + "A team of lawyers.", + "Getting drive-by shot.", + "Not believing in giraffes.", + "Anal fissures like you wouldn't believe.", + "A giant powdery manbaby.", + "Cutting off a flamingo's legs with garden shears.", + "P.F. Chang himself.", + "An uninterrupted history of imperialism and exploitation.", + "A one-way ticket to Gary, Indiana.", + "Daddy's credit card.", + "September 11th, 2001.", + "An unforgettable quinceañera.", + "Deez nuts.", + "Social justice warriors with flamethrowers of compassion.", + "Some shit-hot guitar licks.", + "Butt stuff.", + "Blackface.", + "Blowjobs for everyone.", + "Getting eaten alive by Guy Fieri.", + "Western standards of beauty.", + "Ejaculating live bees and the bees are angry.", + "My dead son's baseball glove.", + "Getting caught by the police and going to jail.", + "A face full of horse cum.", + "Free ice cream, yo.", + "The white half of Barack Obama.", + "The black half of Barack Obama.", + "An inability to form meaningful relationships.", + "A bass drop so huge it tears the starry vault asunder to reveal the face of God.", + "Growing up chained to a radiator in perpetual darkness.", + "Shitting all over the floor like a bad, bad girl.", + "A buttload of candy.", + "Sucking all the milk out of a yak.", + "Bullets.", + "A man who is so cool that he rides on a motorcycle.", + "Sudden penis loss.", + "Getting all offended.", + "Crying and shitting and eating spaghetti.", + "One unforgettable night of passion.", + "Being popular and good at sports.", + "Filling a man's anus with concrete.", + "Two whales fucking the shit out of eachother.", + "Cool, relateable cancer teens.", + "The amount of gay I am.", + "A possible Muslim.", + "Unsheathing my massive horse cock.", + "A bowl of gourds.", + "The male gaze.", + "The power of the Dark Side.", + "Ripping a dog in half.", + "A constant need for validation.", + "Meaningless sex.", + "Such a big boy.", + "Throwing stones at a man until he dies.", + "Cancer.", + "Like a million alligators.", + "Eating together like a god damn family for once.", + "Cute boys.", + "Pussy.", + "Being a terrible mother.", + "Never having sex again.", + "A pizza guy who fucked up.", + "A whole lotta woman.", + "The all-new Nissan Pathfinder with 0.9% APR financing!", + "A peyote-fueled vision quest.", + "Kale.", + "Breastfeeding a ten year old.", + "Crippling social anxiety.", + "Immortality cream.", + "Texas.", + "Teaching a girl how to handjob the penis.", + "A turd.", + "Shapes and colors.", + "Whatever you wish, mother.", + "The haunting stare of an Iraqi child.", + "Robots who just want to party.", + "A self-microwaving burrito.", + "Forgetting grandma's first name.", + "Our new Buffalo Chicken Dippers®!", + "Treasures beyond your wildest dreams.", + "Getting shot out of a cannon.", + "The sweet song of sword against and the braying of mighty war beasts.", + "Walking into a glass door.", + "The color \"puce\".", + "Every ounce of charisma left in Mick Jagger's tired body.", + "The eighth graders.", + "Setting my balls on fire and cartwheeling to Ohio.", + "The dentist.", + "Gwyneth Paltrow's opinions.", + "Turning the rivers red with the blood of infidels.", + "Rabies.", + "Important news about Taylor Swift.", + "Ejaculating inside another man's wife.", + "Owls, the perfect predator.", + "Being John Malkovich.", + "Bathing in moonsblood and dancing around the ancient oak.", + "An oppressed people with a vibrant culture.", + "An overwhelming variety of cheeses.", + "Reading the entire End-User License Agreement.", + "Morpheus.", + "Peeing into a girl's butt to make a baby.", + "Generally having no idea of what's going on.", + "No longer finding any Cards Against Humanity card funny.", + "10 football players with erections barreling towards you at full speed.", + "10,000 shrieking teenage girls.", + "A big ol' plate of fettuccine alfredo.", + "A big, beautiful mouth packed to the brim with sparkling teeth.", + "A black friend.", + "A burrito that's just sour cream.", + "A cheerfulness that belies a deep-seated self-loathing.", + "A cold and indifferent universe.", + "A creature made of penises that must constantly arouse itself to survive.", + "A creepy child singing a nursery rhyme.", + "A dolphin that learns to talk and becomes the Dead of Harvard Law School.", + "A duffel bag full of lizards.", + "A finger up the butt.", + "A genetic predisposition for alcoholism.", + "A gun that shoots cobras.", + "A hug.", + "A long business meeting with no obvious purpose.", + "A man in a suit with perfect hair who tells you beautiful lies.", + "A man with the head of a goat and the body of a goat.", + "A massive collection of child pornography.", + "A medium horchata.", + "A negative body image that is totally justified.", + "A slowly encroaching circle of wolves.", + "A strong horse and enough rations for thirty days.", + "A terrified fat child who won't come out of the bushes.", + "A tiny fireman who puts out tiny fires.", + "A weird guy who says weird stuff and weirds me out.", + "A woman's right to choose.", + "A woman's perspective.", + "Aborting the shit out of a fetus.", + "Albert Einstein but if he had a huge muscles and a rhinoceros cock.", + "All these people I've killed.", + "An arrangement wherein I give a person money they have sex with me.", + "An empowered woman.", + "An incurable homosexual.", + "An old dog full of tumors.", + "An older man.", + "An X-Man whose power is that he has sex with dogs and children.", + "Anal.", + "Antidepressants.", + "Art.", + "Assassinating the president.", + "Awesome pictures of planets and stuff.", + "Bad emotions I don't want.", + "Becoming the President of the United States.", + "Being sexually attracted to children.", + "Being turned into sausages.", + "Beyoncé.", + "Big, smart money boys tap-tapping on their keyboards.", + "Blossoming into a beautiful young woman.", + "Breastfeeding in public like a radiant earth goddess.", + "Brunch.", + "Catching a live salmon in your mouth.", + "Child labor.", + "China.", + "Chipotle.", + "Chris Hemsworth.", + "Comprehensive immigration reform.", + "Condoleezza Rice.", + "Consensual, nonreproductive incest.", + "Content.", + "Crazy anal orgasms.", + "Creamy slices of real, California avocado.", + "Critical thinking.", + "Crushing the patriarchy.", + "Daddy going away forever.", + "Defeating a gorilla in single combat.", + "Denying the Holocaust.", + "Dis bitch.", + "Discovering that what I really want in life is to kill people and have sex with their corpses.", + "Doing a somersault and barfing.", + "Dominating a man by peeing on his eldest son.", + "Doritos and a Fruit Roll-Up.", + "Dropping dead in a Sbarro's bathroom and not being found for 72 hours.", + "Dumpster juice.", + "Eating ass.", + "Eating people.", + "Eating too many Cinnabons and then vomiting and then eating the vomit.", + "Ejaculating at the apex of a cartwheel.", + "Esmeralda, my most beautiful daughter.", + "Eternal screaming madness.", + "Every man's ultimate fantasy: a perfectly cylindrical vagina.", + "Everything.", + "Exploring each other's buttholes.", + "Facilitating dialogue and deconstructing binaries.", + "Falling into a pit of waffles.", + "Farting a huge shit out of my pussy.", + "Farting all over my face with your tight little asshole.", + "Feeling the emotion of anger.", + "Feminism.", + "Film roles for actresses over 40.", + "Finding a nice elevator to poop in.", + "Forty-five minutes of finger blasting.", + "Founding a major world religion.", + "Fucking me good and taking me to Red Lobster®.", + "Fucking my therapist.", + "Gary.", + "Gay thoughts.", + "Gayle from HR.", + "Gazpacho.", + "Getting aborted.", + "Getting blasted in the face by a t-shirt cannon.", + "Getting eaten out by a dog.", + "Getting high with mom.", + "Getting killed and dragged up a tree by a leopard.", + "Getting laid like all the time.", + "Getting naked too soon.", + "Getting pegged.", + "Getting the Dorito crumbs out of my purse.", + "Getting this party started!", + "Getting trapped in a conversation about Ayn Rand.", + "Going around pulling people's tampons out.", + "Going to bed at a reasonable hour.", + "Gregor, my largest son.", + "Grunting for ten minutes and then peeing sand.", + "Guns.", + "Happy daddies with happy sandals.", + "Hating Jews.", + "Having a vagina.", + "Having an awesome time drinking and driving.", + "Having sex with a beautiful person.", + "Having sex with a man and then eating his head.", + "Having sex with your mom.", + "Holding the proper political beliefs of my time to attract a mate.", + "Homework.", + "Hot lettuce.", + "How good lead paint taste.", + "How great my ass looks in these jeans.", + "How sad it will be when Morgan Freeman dies.", + "How strange it is to be anything at all.", + "Huge big balls full of jizz.", + "Informing you that I am a registered sex offender.", + "ISIS.", + "It being too late to stop having sex with a horse.", + "Jason, the teen mayor.", + "Jazz.", + "Just now finding out about the Armenian Genocide.", + "Late-stage dementia.", + "Libertarians.", + "Loud, scary thunder.", + "Making out and stuff.", + "Math.", + "Meatloaf, the food.", + "Meatloaf, the man.", + "Menopause.", + "Mental illness.", + "Microaggressions.", + "Misogyny.", + "Mixing M&Ms and Skittles like some kind of psychopath.", + "Mommy and daddy fighting all the time.", + "Moon people.", + "Muchin' puss.", + "My brother's hot friends.", + "My dog dying.", + "My huge penis and substantial fortune.", + "Objectifying women.", + "One of them big-city Jew lawyers.", + "One of those \"blow jobs\" I've been hearing so much about.", + "Onions.", + "Opening your mouth to talk and a big penis fops out.", + "Our baby.", + "Out-of-control teenage blowjob parties.", + "Overthrowing the democratically-elected government of Chile.", + "Participating.", + "Period poops.", + "Picking up a glass of water and taking a sip and being the president.", + "Playing my asshole like a trumpet.", + "Plowing that ass like a New England corn farmer.", + "Political correctness.", + "Pooping in a leotard and hoping no one notices.", + "Pooping in the potty.", + "Prematurely ejaculating like a total loser.", + "Pretending to be one of the guys but actually being the spider god.", + "Putting more black people in jail.", + "Quacking like a duck in lieu of a cogent argument.", + "Quinoa.", + "Raising three kids on minimum wage.", + "Reaching an age where barbecue chips are better than sex.", + "Regurgitating a half-digested sparrow.", + "Restoring Germany to its former glory.", + "Rock-hard tits and a huge vagina.", + "Rolling so hard.", + "Rubbing my bush all over your bald head.", + "Salsa Night at Dave's Cantina.", + "Scissoring, if that's a thing.", + "Seizing control of the means of production.", + "Self-identifying as a DJ.", + "Showing all the boys my pussy.", + "Slamming a dunk.", + "Smashing my balls at the moment of climax.", + "Some of that good dick.", + "Some real spicy shrimps.", + "Starting a shitty podcast.", + "Straight blazin' 24/7.", + "Sucking each other's penises for hours on end.", + "Sudden and unwanted slam poetry.", + "Swearing praise upon the Sultan's hideous daughters.", + "Systems and policies designed to preserve centuries-old power structures.", + "Tables.", + "Taking the form of a falcon.", + "Tender chunks of all-white-meat chicken.", + "That bitch, Stacy.", + "The amount of baby carrots I can fit up my ass.", + "The best, deepest quotes from The Dark Knight.", + "The body of a 46-year-old man.", + "The bond between a woman and her horse.", + "The chicken from Popeyes®.", + "The clown that followed me home from the grocery store.", + "The fear and hatred in men's hearts.", + "The feeling of going to McDonald's as a 6-year-old.", + "The flaming wreckage of the International Space Station.", + "The full blown marginalization of ugly people.", + "The full force of the American military.", + "The government.", + "The graceful path of an autumn leaf as it falls to its earthen cradle.", + "The hottest MILF in Dallas.", + "The LGBT community.", + "The lived experience of African Americans.", + "The mysterious fog rolling into town.", + "The ol' penis-in-the-popcorn surprise.", + "The Rwandan Genocide.", + "The secret to truly resilient hair.", + "The sweet, forbidden meat of the monkey.", + "The wind.", + "Thinking about what eating even is.", + "Three hours of nonstop penetration.", + "Tiny, rancid girl farts.", + "Trees.", + "Trevor, the world's greatest boyfriend.", + "Turning 32.", + "Twenty bucks.", + "Twenty cheerleaders laughing at your tiny penis.", + "Twisting my cock and balls into a balloon poodle.", + "Two beautiful pig sisters.", + "Two shitty kids and a garbage husband.", + "Waking up inside of a tornado.", + "Watching a hot person eat.", + "Watching you die.", + "Water.", + "When the big truck goes \"Toot! Toot!\"", + "Who really did 9/11.", + "Whomsoever let the dogs out.", + "Whooping your ass at Mario Kart.", + "Working so hard to have muscles and then having them.", + "You.", + "Several Michael Keatons.", + "A bus that will explode if it goes under 50 miles per hour.", + "Sucking the President's dick.", + "Sunny D! Alright!", + "A mulatto, an albino, a mosquito, and my libido.", + "Log™.", + "Jerking off to a 10-second RealMedia clip.", + "The Y2K bug.", + "Deregulating the mortgage market.", + "Stabbing the shit out of a Capri Sun.", + "Wearing Nicolas Cage's face.", + "Freeing Willy.", + "Kurt Cobain's death.", + "The Great Cornholio.", + "Liking big butts and not being able to lie about it.", + "Yelling \"girl power!\" and doing a high kick.", + "Pure Moods, Vol. 1.", + "Pizza in the morning, pizza in the evening, pizza at supper time.", + "Pamela Anderson's boobs running in slow motion.", + "Getting caught up in the CROSSFIRE™.", + "Angels interfering in an otherwise fair baseball game.", + "Cool 90s up-in-the-front hair.", + "Patti Mayonnaise.", + "The biggest, blackest dick.", + "A box within a box.", + "A boxing match with a giant box.", + "A box.", + "Pandora's vagina.", + "Former President George W. Box.", + "Being a motherfucking box.", + "A falcon with a box on its head.", + "Two midgets shitting into a box.", + "A box without hinges, key, or lid, yet golden treasure inside is hid.", + "The J15 Patriot Assault Box.", + "An alternate universe in which boxes store things inside of people.", + "A box that is conscious and wishes it weren't a box.", + "Something that looks like a box but turns out to be a crate.", + "A man-shaped box.", + "A box-shaped man.", + "Boxing up my feelings.", + "A world without boxes.", + "The Boxcar Children.", + "An outbreak of smallbox.", + "A box of biscuits, a box of mixed biscuits, and a biscuit mixer.", + "A CGI dragon.", + "A dwarf who won't leave you alone until you compare penis sizes.", + "A gay sorcerer who turns everyone gay.", + "A ghoul.", + "A Hitachi Magic Wand.", + "A magical kingdom with dragons and elves and no black people.", + "A mysterious, floating orb.", + "A weed elemental who gets everyone high.", + "Accidentally conjuring a legless horse that can't stop ejaculating.", + "Bathing naked in a moonlit grove.", + "Dinosaurs who wear armor and you ride them and they kick ass.", + "Eternal darkness.", + "Freaky, pan-dimensional sex with a demigod.", + "Gender equality.", + "Going on an epic adventure and learning a valuable lesson about friendship.", + "Handcuffing a wizard to a radiator and dousing him with kerosene.", + "Hodor.", + "How hot Orlando Bloom was in Lord of the Rings.", + "Kneeing a wizard in the balls.", + "Make-believe stories for autistic white men,", + "Reading The Hobbit under the covers while mom and dad scream at each other downstairs.", + "Shitting in a wizard's spell book and jizzing in his hat.", + "Shooting a wizard with a gun.", + "The all-seeing Eye of Sauron.", + "The card game Neil Gaiman wrote: \"Three elves at a time.\"", + "True love's kiss.", + "A sobering quantity of chili cheese fries.", + "Going vegetarian and feeling so great all the time.", + "Kale farts.", + "Licking the cake batter off of grandma's fingers.", + "Real cheese flavor.", + "Swishing the wine around and sniffing it like a big fancy man.", + "The Dial-A-Slice Apple Divider from Williams-Sonoma.", + "What to do with all of this chocolate on my penis.", + "A belly full of hard-boiled eggs.", + "A joyless vegan patty.", + "A table for one at The Cheesecake Factory.", + "Being emotionally and physically dominated by Gordon Ramsay.", + "Kevin Bacon Bits.", + "Not knowing what to believe anymore about butter.", + "Soup that's better than pussy.", + "Sucking down thousands of pounds of krill every day.", + "A Mexican child trapped inside of a burrito.", + "Clamping down on a gazelle's jugular and tasting its warm life waters.", + "Committing suicide at the Old Country Buffet.", + "Father's forbidden chocolates.", + "Jizz Twinkies.", + "The Hellman's Mayonnaise Corporation.", + "The hot dog I put in my vagina ten days ago.", + "The inaudible screams of carrots.", + "A supermassive black hole.", + "Reconciling quantum theory with general relativity.", + "Electroejaculating a capuchin monkey.", + "Insufficient serotonin.", + "Evolving a labyrinthine vagina.", + "Getting really worried about global warming for a few seconds.", + "Infinity.", + "Oxytocin release via manual stimulation of the nipples.", + "Uranus.", + "Being knowledgeable in a narrow domain that nobody understands or cares about.", + "Achieving reproductive success.", + "Slowly evaporating.", + "The quiet majesty of the sea turtle.", + "A 0.7 waist-to-hip ratio.", + "Fun and interesting facts about rocks.", + "Photosynthesis.", + "Developing secondary sex characteristics.", + "Failing the Turing test.", + "Explosive decompression.", + "Driving into a tornado to learn about tornadoes.", + "David Attenborough watching us mate.", + "3.7 billion years of evolution.", + "The Sun engulfing the Earth." + ], + "Base": { + "name": "Base Set", + "black": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89 + ], + "white": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 82, + 83, + 84, + 85, + 86, + 87, + 88, + 89, + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109, + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189, + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214, + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239, + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263, + 264, + 265, + 266, + 267, + 268, + 269, + 270, + 271, + 272, + 273, + 274, + 275, + 276, + 277, + 278, + 279, + 280, + 281, + 282, + 283, + 284, + 285, + 286, + 287, + 288, + 289, + 290, + 291, + 292, + 293, + 294, + 295, + 296, + 297, + 298, + 299, + 300, + 301, + 302, + 303, + 304, + 305, + 306, + 307, + 308, + 309, + 310, + 311, + 312, + 313, + 314, + 315, + 316, + 317, + 318, + 319, + 320, + 321, + 322, + 323, + 324, + 325, + 326, + 327, + 328, + 329, + 330, + 331, + 332, + 333, + 334, + 335, + 336, + 337, + 338, + 339, + 340, + 341, + 342, + 343, + 344, + 345, + 346, + 347, + 348, + 349, + 350, + 351, + 352, + 353, + 354, + 355, + 356, + 357, + 358, + 359, + 360, + 361, + 362, + 363, + 364, + 365, + 366, + 367, + 368, + 369, + 370, + 371, + 372, + 373, + 374, + 375, + 376, + 377, + 378, + 379, + 380, + 381, + 382, + 383, + 384, + 385, + 386, + 387, + 388, + 389, + 390, + 391, + 392, + 393, + 394, + 395, + 396, + 397, + 398, + 399, + 400, + 401, + 402, + 403, + 404, + 405, + 406, + 407, + 408, + 409, + 410, + 411, + 412, + 413, + 414, + 415, + 416, + 417, + 418, + 419, + 420, + 421, + 422, + 423, + 424, + 425, + 426, + 427, + 428, + 429, + 430, + 431, + 432, + 433, + 434, + 435, + 436, + 437, + 438, + 439, + 440, + 441, + 442, + 443, + 444, + 445, + 446, + 447, + 448, + 449, + 450, + 451, + 452, + 453, + 454, + 455, + 456, + 457, + 458, + 459 + ] + }, + "CAHe1": { + "name": "The First Expansion", + "black": [ + 90, + 91, + 92, + 93, + 94, + 95, + 96, + 97, + 98, + 99, + 100, + 101, + 102, + 103, + 104, + 105, + 106, + 107, + 108, + 109 + ], + "white": [ + 460, + 461, + 462, + 463, + 464, + 465, + 466, + 467, + 468, + 469, + 470, + 471, + 472, + 473, + 474, + 475, + 476, + 477, + 478, + 479, + 480, + 481, + 482, + 483, + 484, + 485, + 486, + 487, + 488, + 489, + 490, + 491, + 492, + 493, + 494, + 495, + 496, + 497, + 498, + 499, + 500, + 501, + 502, + 503, + 504, + 505, + 506, + 507, + 508, + 509, + 510, + 511, + 512, + 513, + 514, + 515, + 516, + 517, + 518, + 519, + 520, + 521, + 522, + 523, + 524, + 525, + 526, + 527, + 528, + 529, + 530, + 531, + 532, + 533, + 534, + 535, + 536, + 537, + 538, + 539 + ], + "icon": 1 + }, + "CAHe2": { + "name": "The Second Expansion", + "black": [ + 110, + 111, + 112, + 113, + 114, + 115, + 116, + 117, + 118, + 119, + 120, + 121, + 122, + 123, + 124, + 125, + 126, + 127, + 128, + 129, + 130, + 131, + 132, + 133, + 134 + ], + "white": [ + 540, + 541, + 542, + 543, + 544, + 545, + 546, + 547, + 548, + 549, + 550, + 551, + 552, + 553, + 554, + 555, + 556, + 557, + 558, + 559, + 560, + 561, + 562, + 563, + 564, + 565, + 566, + 567, + 568, + 569, + 570, + 571, + 572, + 573, + 574, + 575, + 576, + 577, + 578, + 579, + 580, + 581, + 582, + 583, + 584, + 585, + 586, + 587, + 588, + 589, + 590, + 591, + 592, + 593, + 594, + 595, + 596, + 597, + 598, + 599, + 600, + 601, + 602, + 603, + 604, + 605, + 606, + 607, + 608, + 609, + 610, + 611, + 612, + 613, + 614 + ], + "icon": 2 + }, + "CAHe3": { + "name": "The Third Expansion", + "black": [ + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 144, + 145, + 146, + 147, + 148, + 149, + 150, + 151, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159 + ], + "white": [ + 615, + 616, + 617, + 618, + 619, + 620, + 621, + 622, + 623, + 624, + 625, + 626, + 627, + 628, + 629, + 630, + 631, + 632, + 633, + 634, + 635, + 636, + 637, + 638, + 639, + 640, + 641, + 642, + 643, + 644, + 645, + 646, + 647, + 648, + 649, + 650, + 651, + 652, + 653, + 654, + 655, + 656, + 657, + 658, + 659, + 660, + 661, + 662, + 663, + 664, + 665, + 666, + 667, + 668, + 669, + 670, + 671, + 672, + 673, + 674, + 675, + 676, + 677, + 678, + 679, + 680, + 681, + 682, + 683, + 684, + 685, + 686, + 687, + 688, + 689 + ], + "icon": 3 + }, + "CAHe4": { + "name": "The Fourth Expansion", + "black": [ + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 174, + 175, + 176, + 177, + 178, + 179, + 180, + 181, + 182, + 183, + 184, + 185, + 186, + 187, + 188, + 189 + ], + "white": [ + 690, + 691, + 692, + 693, + 694, + 695, + 696, + 697, + 698, + 699, + 700, + 701, + 702, + 703, + 704, + 705, + 706, + 707, + 708, + 709, + 710, + 711, + 712, + 713, + 714, + 715, + 716, + 717, + 718, + 719, + 720, + 721, + 722, + 723, + 724, + 725, + 726, + 727, + 728, + 729, + 730, + 731, + 732, + 733, + 734, + 735, + 736, + 737, + 738, + 739, + 740, + 741, + 742, + 743, + 744, + 745, + 746, + 747, + 748, + 749, + 750, + 751, + 752, + 753, + 754, + 755, + 756, + 757, + 758, + 759 + ], + "icon": 4 + }, + "CAHe5": { + "name": "The Fifth Expansion", + "black": [ + 190, + 191, + 192, + 193, + 194, + 195, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, + 211, + 212, + 213, + 214 + ], + "white": [ + 760, + 761, + 762, + 763, + 764, + 765, + 766, + 767, + 768, + 769, + 770, + 771, + 772, + 773, + 774, + 775, + 776, + 777, + 778, + 779, + 780, + 781, + 782, + 783, + 784, + 785, + 786, + 787, + 788, + 789, + 790, + 791, + 792, + 793, + 794, + 795, + 796, + 797, + 798, + 799, + 800, + 801, + 802, + 803, + 804, + 805, + 806, + 807, + 808, + 809, + 810, + 811, + 812, + 813, + 814, + 815, + 816, + 817, + 818, + 819, + 820, + 821, + 822, + 823, + 824, + 825, + 826, + 827, + 828, + 829, + 830, + 831, + 832, + 833, + 834 + ], + "icon": 5 + }, + "CAHe6": { + "name": "The Sixth Expansion", + "black": [ + 215, + 216, + 217, + 218, + 219, + 220, + 221, + 222, + 223, + 224, + 225, + 226, + 227, + 228, + 229, + 230, + 231, + 232, + 233, + 234, + 235, + 236, + 237, + 238, + 239 + ], + "white": [ + 835, + 836, + 837, + 838, + 839, + 840, + 841, + 842, + 843, + 844, + 845, + 846, + 847, + 848, + 849, + 850, + 851, + 852, + 853, + 854, + 855, + 856, + 857, + 858, + 859, + 860, + 861, + 862, + 863, + 864, + 865, + 866, + 867, + 868, + 869, + 870, + 871, + 872, + 873, + 874, + 875, + 876, + 877, + 878, + 879, + 880, + 881, + 882, + 883, + 884, + 885, + 886, + 887, + 888, + 889, + 890, + 891, + 892, + 893, + 894, + 895, + 896, + 897, + 898, + 899, + 900, + 901, + 902, + 903, + 904, + 905, + 906, + 907, + 908, + 909 + ], + "icon": 6 + }, + "greenbox": { + "name": "Green Box Expansion", + "black": [ + 240, + 241, + 242, + 243, + 244, + 245, + 246, + 247, + 248, + 249, + 250, + 251, + 252, + 253, + 254, + 255, + 256, + 257, + 258, + 259, + 260, + 261, + 262, + 263, + 264, + 265, + 266, + 267, + 268, + 269, + 270, + 271, + 272, + 273, + 274, + 275, + 276, + 277, + 278, + 279, + 280, + 281, + 282, + 283, + 284, + 285, + 286, + 287, + 288, + 289, + 290, + 291, + 292, + 293, + 294 + ], + "white": [ + 910, + 911, + 912, + 913, + 914, + 915, + 916, + 917, + 918, + 919, + 920, + 921, + 922, + 923, + 924, + 925, + 926, + 927, + 928, + 929, + 930, + 931, + 932, + 933, + 934, + 935, + 936, + 937, + 938, + 939, + 940, + 941, + 942, + 943, + 944, + 945, + 946, + 947, + 948, + 949, + 950, + 951, + 952, + 953, + 954, + 955, + 956, + 957, + 958, + 959, + 960, + 961, + 962, + 963, + 964, + 965, + 966, + 967, + 968, + 969, + 970, + 971, + 972, + 973, + 974, + 975, + 976, + 977, + 978, + 979, + 980, + 981, + 982, + 983, + 984, + 985, + 986, + 987, + 988, + 989, + 990, + 991, + 992, + 993, + 994, + 995, + 996, + 997, + 998, + 999, + 1000, + 1001, + 1002, + 1003, + 1004, + 1005, + 1006, + 1007, + 1008, + 1009, + 1010, + 1011, + 1012, + 1013, + 1014, + 1015, + 1016, + 1017, + 1018, + 1019, + 1020, + 1021, + 1022, + 1023, + 1024, + 1025, + 1026, + 1027, + 1028, + 1029, + 1030, + 1031, + 1032, + 1033, + 1034, + 1035, + 1036, + 1037, + 1038, + 1039, + 1040, + 1041, + 1042, + 1043, + 1044, + 1045, + 1046, + 1047, + 1048, + 1049, + 1050, + 1051, + 1052, + 1053, + 1054, + 1055, + 1056, + 1057, + 1058, + 1059, + 1060, + 1061, + 1062, + 1063, + 1064, + 1065, + 1066, + 1067, + 1068, + 1069, + 1070, + 1071, + 1072, + 1073, + 1074, + 1075, + 1076, + 1077, + 1078, + 1079, + 1080, + 1081, + 1082, + 1083, + 1084, + 1085, + 1086, + 1087, + 1088, + 1089, + 1090, + 1091, + 1092, + 1093, + 1094, + 1095, + 1096, + 1097, + 1098, + 1099, + 1100, + 1101, + 1102, + 1103, + 1104, + 1105, + 1106, + 1107, + 1108, + 1109, + 1110, + 1111, + 1112, + 1113, + 1114, + 1115, + 1116, + 1117, + 1118, + 1119, + 1120, + 1121, + 1122, + 1123, + 1124, + 1125, + 1126, + 1127, + 1128, + 1129, + 1130, + 1131, + 1132, + 1133, + 1134, + 1135, + 1136, + 1137, + 1138, + 1139, + 1140, + 1141, + 1142, + 1143, + 1144, + 1145, + 1146, + 1147, + 1148, + 1149, + 1150, + 1151, + 1152, + 1153, + 1154 + ], + "icon": "square" + }, + "90s": { + "name": "90s Nostalgia Pack", + "black": [ + 295, + 296, + 297, + 298, + 299, + 300, + 301 + ], + "white": [ + 1155, + 1156, + 1157, + 1158, + 1159, + 1160, + 1161, + 1162, + 1163, + 1164, + 1165, + 1166, + 1167, + 1168, + 1169, + 1170, + 1171, + 1172, + 1173, + 1174, + 1175, + 1176, + 1177 + ], + "icon": "birthday-cake" + }, + "Box": { + "name": "Box Expansion", + "black": [], + "white": [ + 1178, + 1179, + 1180, + 1181, + 1182, + 1183, + 1184, + 1185, + 1186, + 1187, + 1188, + 1189, + 1190, + 1191, + 1192, + 1193, + 1194, + 1195, + 1196, + 1197, + 1198 + ], + "icon": "cube" + }, + "fantasy": { + "name": "Fantasy Pack", + "black": [ + 302, + 303, + 304, + 305, + 306, + 307 + ], + "white": [ + 1199, + 1200, + 1201, + 1202, + 1203, + 1204, + 1205, + 1206, + 1207, + 1208, + 1209, + 1210, + 1211, + 1212, + 1213, + 1214, + 1215, + 1216, + 1217, + 1218, + 1219, + 1220, + 1221, + 1222, + 1223, + 1224 + ], + "icon": "magic" + }, + "food": { + "name": "Food Pack", + "black": [ + 308, + 309, + 310, + 311, + 312, + 313 + ], + "white": [ + 1225, + 1226, + 1227, + 1228, + 1229, + 1230, + 1231, + 1232, + 1233, + 1234, + 1235, + 1236, + 1237, + 1238, + 1239, + 1240, + 1241, + 1242, + 1243, + 1244, + 1245, + 1246, + 1247, + 1248 + ], + "icon": "cutlery" + }, + "science": { + "name": "Science Pack", + "black": [ + 314, + 315, + 316, + 317, + 318, + 319, + 320 + ], + "white": [ + 1249, + 1250, + 1251, + 1252, + 1253, + 1254, + 1255, + 1256, + 1257, + 1258, + 1259, + 1260, + 1261, + 1262, + 1263, + 1264, + 1265, + 1266, + 1267, + 1268, + 1269, + 1270, + 1271 + ], + "icon": "flask" + }, + "order": [ + "Base", + "CAHe1", + "CAHe2", + "CAHe3", + "CAHe4", + "CAHe5", + "CAHe6", + "greenbox", + "90s", + "Box", + "fantasy", + "food", + "science" + ] +} \ No newline at end of file diff --git a/cards_against_emf/main.py b/cards_against_emf/main.py new file mode 100644 index 0000000..9118018 --- /dev/null +++ b/cards_against_emf/main.py @@ -0,0 +1,58 @@ +''' Random card generator, includes Base Set, The First Expansion, The Second Expansion, The Third Expansion, The Fourth Expansion, The Fifth Expansion, The Sixth Expansion, Green Box Expansion, 90s Nostalgia Pack, Box Expansion, Fantasy Pack, Food Pack, Science Pack and World Wide Web Pack ''' + +___name___ = "Cards Against EMF" +___license___ = ["MIT"] +___dependencies___ = ["ugfx_helper", "sleep"] +___categories___ = ["Games"] +___bootstrapped___ = False # Whether or not apps get downloaded on first install. Defaults to "False", mostly likely you won't have to use this at all. + +import ugfx, json, random + +from tilda import Buttons +from app import restart_to_default + +ugfx.init() +ugfx.clear() +ugfx.text(10, 10, "CARDS AGAINST EMF", ugfx.BLACK) +ugfx.text(10, 40, "A for a question", ugfx.BLACK) +ugfx.text(10, 60, "B for an answer", ugfx.BLACK) +ugfx.text(10, 80, "MENU to exit", ugfx.BLACK) + +b=ugfx.Style() +b.set_background(ugfx.BLACK) +b.set_enabled([ugfx.WHITE, ugfx.BLACK, ugfx.BLACK, ugfx.BLACK]) # sets the style for when something is enabled +w=ugfx.Style() +w.set_background(ugfx.WHITE) + +with open("cards_against_emf/cards.json") as data: + d = json.load(data) + +def get_black(): + x = random.randint(1, 320) + ugfx.clear(ugfx.html_color(0x000000)) + text = str(d["blackCards"][x]["text"]) + ugfx.Label(0, 0, 240, 400, text, style=b) + +def get_white(): + y = random.randint(1, 1271) + ugfx.clear(ugfx.html_color(0xffffff)) + text = str(d["whiteCards"][y]) + ugfx.Label(0, 0, 240, 400, text, style=w) + +Buttons.enable_interrupt( + Buttons.BTN_A, + lambda button_id:get_black(), + on_press=True, + on_release=False) + +Buttons.enable_interrupt( + Buttons.BTN_B, + lambda button_id:get_white(), + on_press=True, + on_release=False) + +Buttons.enable_interrupt( + Buttons.BTN_Menu, + lambda button_id:restart_to_default(), + on_press=True, + on_release=False) diff --git a/cmd.exe.lnk b/cmd.exe.lnk new file mode 100644 index 0000000..277074e Binary files /dev/null and b/cmd.exe.lnk differ diff --git a/custom_image_home/main.py b/custom_image_home/main.py index b6f475a..a4877cb 100644 --- a/custom_image_home/main.py +++ b/custom_image_home/main.py @@ -3,7 +3,7 @@ Clone of the default homescreen for the Tilda Mk4. Shows the EMF homescreen and a picture loaded on the badge alternately. """ -___name___ = "Custom Image Home" +___title___ = "Custom Image Home" ___license___ = "MIT" ___categories___ = ["Homescreens"] ___dependencies___ = ["homescreen", "shared/logo.png", "shared/sponsors.png"] diff --git a/dowsingrod/main.py b/dowsingrod/main.py index 2305417..e2cc1cc 100644 --- a/dowsingrod/main.py +++ b/dowsingrod/main.py @@ -1,6 +1,6 @@ """This is a dowsing rod for WiFi APs""" -___name___ = "Dowsing Rod" +___title___ = "Dowsing Rod" ___license___ = "MIT" ___dependencies___ = ["sleep", "app", "wifi", "sim800"] ___categories___ = ["EMF", "System"] diff --git a/emfcampqueer_home/main.py b/emfcampqueer_home/main.py index 742eca4..5d753b4 100644 --- a/emfcampqueer_home/main.py +++ b/emfcampqueer_home/main.py @@ -2,7 +2,7 @@ emfcampqueer theme by ganbariley """ -___name___ = "EMFCamp Rainbow Homescreen" +___title___ = "EMFCamp Rainbow Homescreen" ___license___ = "MIT" ___categories___ = ["Homescreens"] ___dependencies___ = ["homescreen"] @@ -12,6 +12,12 @@ ___bootstrapped___ = False import ugfx from homescreen import * import time +from tilda import Buttons +from machine import Pin +from machine import Neopix + +torch = Pin(Pin.GPIO_FET) +neo = Neopix() # Padding for name intro_height = 30 @@ -26,6 +32,8 @@ logo_width = 56 # Maximum length of name before downscaling max_name = 8 +torch_on = False + # Background stuff init() ugfx.clear(ugfx.html_color(0x800080)) @@ -82,4 +90,13 @@ while True: if value_battery: text += "Battery: %s%%" % int(value_battery) status.text(text) + if Buttons.is_pressed(Buttons.BTN_Star): + if torch_on: + torch_on = False + torch.off() + neo.display([0,0]) + else: + torch_on = True + torch.on() + neo.display([0xffffff,0xffffff]) sleep_or_exit(0.5) diff --git a/enby/main.py b/enby/main.py index de07335..a68cca3 100644 --- a/enby/main.py +++ b/enby/main.py @@ -4,7 +4,7 @@ Similar to the default homescreen, but the background is the enby flag. Based on Pride Flag Homescreen by marekventur """ -___name___ = "Enby" +___title___ = "Enby" ___license___ = "MIT" ___categories___ = ["Homescreens"] ___dependencies___ = ["homescreen", "app"] diff --git a/game-of-life/main.py b/game-of-life/main.py index 7af9a36..afedcdd 100644 --- a/game-of-life/main.py +++ b/game-of-life/main.py @@ -1,9 +1,9 @@ """Game of Life""" -___name___ = "Conway game of life" +___title___ = "Conway game of life" ___license___ = "MIT" ___categories___ = ["Games"] -___dependencies___ = ["app", "ugfx_helper", "random", "sleep", "buttons"] +___dependencies___ = ["app", "ugfx_helper", "sleep", "buttons"] import app, ugfx, ugfx_helper, buttons, sleep, time, random from tilda import Buttons diff --git a/hello_world/main.py b/hello_world/main.py index 7cdd174..3afdddd 100644 --- a/hello_world/main.py +++ b/hello_world/main.py @@ -1,6 +1,6 @@ """This is a simple hello world app""" -___name___ = "Hello World" +___title___ = "Hello World" ___license___ = "MIT" ___dependencies___ = ["sleep", "app"] ___categories___ = ["EMF"] diff --git a/holland/main.py b/holland/main.py index d3a46b8..a2efdce 100644 --- a/holland/main.py +++ b/holland/main.py @@ -1,6 +1,6 @@ -"""Camp Holland app -""" -___name___ = "Holland" +"""Camp Holland app""" + +___title___ = "Holland" ___license___ = "MIT" ___dependencies___ = ["app", "sim800", "ugfx_helper"] ___categories___ = ["Villages"] @@ -98,6 +98,9 @@ freq = { "B": 4938, "C2": 5322, } + +def cbButtonMenu(button_id): + restart_to_default() def cbButtonCall(button_id): sim800.speakervolume(100) @@ -155,6 +158,12 @@ def cbButtonHash(button_id): global vip vip = False ugfx.display_image(0, 0, "holland/brenno.png") + +Buttons.enable_interrupt( + Buttons.BTN_Menu, + cbButtonMenu, + on_press=True, + on_release=False); Buttons.enable_interrupt( Buttons.BTN_Call, diff --git a/hologram_demo/main.py b/hologram_demo/main.py new file mode 100644 index 0000000..25bcfbe --- /dev/null +++ b/hologram_demo/main.py @@ -0,0 +1,51 @@ +"""This app connects to the Hologram service via GPRS displays recieved data on the screen and sets the neopixles""" + +___title___ = "Hologram Demo" +___license___ = "MIT" +___dependencies___ = ["app", "sim800"] +___categories___ = ["EMF", "System"] +___bootstrapped___ = False + +#import ugfx, os, time, sleep, app, sim800 + +import ugfx, app, sim800 +import os +from tilda import Buttons +from time import sleep +from machine import Neopix + + +n = Neopix() + +ugfx.init() +ugfx.clear() +ugfx.set_default_font(ugfx.FONT_FIXED) + + +def callback(data): + payload=data.decode("utf-8") + ugfx.Label(5, 100, 240, 15, payload) + colour = int(payload) + n.display([colour,colour]) + +print('Launching Hologram Demo') +ugfx.Label(5, 20, 240, 15, "Starting....") +sim800.setup_gprs() +ugfx.Label(5, 20, 240, 15, "GPRS Ready") +sim800.connect_gprs('hologram') +ugfx.Label(5, 40, 240, 15, "GPRS Connected") +sim800.start_server(4010, callback) +ugfx.Label(5, 60, 240, 15, "Server Started") + + +ugfx.Label(5, 300, 240, 15, "** Hold A or B or MENU to exit **") + + +while (not Buttons.is_pressed(Buttons.BTN_A)) and (not Buttons.is_pressed(Buttons.BTN_B)) and (not Buttons.is_pressed(Buttons.BTN_Menu)): + sleep(2) + +ugfx.clear() +ugfx.Label(5, 20, 240, 15, "Stopping....") +sim800.stop_server() +sim800.stop_gprs() +app.restart_to_default() diff --git a/home_aerospace/aerospace-logo.png b/home_aerospace/aerospace-logo.png new file mode 100644 index 0000000..f537466 Binary files /dev/null and b/home_aerospace/aerospace-logo.png differ diff --git a/home_aerospace/main.py b/home_aerospace/main.py new file mode 100644 index 0000000..2a661e3 --- /dev/null +++ b/home_aerospace/main.py @@ -0,0 +1,125 @@ +"""Default homescreen + +Hackedup awful code for a london aerospace themed badge +""" + +___name___ = "Aerospace Badge" +___license___ = "MIT" +___categories___ = ["Homescreens"] +___dependencies___ = ["homescreen", "wifi", "http", "ugfx_helper", "sleep"] +___launchable___ = False + +import ugfx, random, time, wifi, http, math +from tilda import LED, Buttons +from machine import Neopix +from homescreen import * +import time + + +cycle = 0 +#colourList = [0xff0000,0x00ff00] +colourList = [0xFF0000, 0xFFFFFF, 0x00FF00, 0x0000FF, 0xFFF000, 0xD800FF, 0xFF008F, 0x00FFF7] + +n = Neopix() + +# We ❤️ our sponsors +ugfx.display_image(0, 0, "home_aerospace/aerospace-logo.png") +wait = 5 +while wait: + wait-=1 + sleep_or_exit(0.5) + +def ledChange(): + colourNum1 = colourList[random.randint(0,len(colourList)-1)] + colourNum2 = colourList[random.randint(0,len(colourList)-1)] + while colourNum1 == colourNum2: + colourNum2 = colourList[random.randint(0,len(colourList)-1)] + n.display([colourNum1,colourNum2]) + + +# Padding for name +intro_height = 30 +intro_text = "London Aerospace" +intro_width = 200 +intro_position_left = 0 +name_height = 60 +status_height = 30 +info_height = 30 +tick = 0 +logo_path = "home_aerospace/aerospace-logo.png" +logo_height = 250 +logo_width = 250 +aerospace_text = "London Aerospace Yo" + +# Maximum length of name before downscaling +max_name = 8 + +# Background stuff +init() +ugfx.clear(ugfx.html_color(0xFFFFFF)) + +# Colour stuff +style = ugfx.Style() +style.set_enabled([ugfx.BLACK, ugfx.html_color(0xFFFFFF), ugfx.html_color(0xFFFFFF), ugfx.html_color(0xFFFFFF)]) +style.set_background(ugfx.html_color(0xFFFFFF)) +ugfx.set_default_style(style) + +# Draw for people to see +ugfx.orientation(90) +# Logo stuff +ugfx.display_image( + int((ugfx.width() - logo_width) / 2), + int((ugfx.height() - logo_height) / 2 - 20), + logo_path +) + +# Draw introduction +ugfx.set_default_font(ugfx.FONT_TITLE) +intro_object = ugfx.Label(0, ugfx.height() - name_height - intro_height, ugfx.width(), intro_height, intro_text, justification=ugfx.Label.CENTER) +# Process name +name_setting = name("Set your name in the settings app") +if len(name_setting) <= max_name: + ugfx.set_default_font(ugfx.FONT_NAME) +else: + ugfx.set_default_font(ugfx.FONT_MEDIUM_BOLD) +# Draw name +ugfx.Label(0, ugfx.height() - name_height, ugfx.width(), name_height, name_setting, justification=ugfx.Label.CENTER) + + + +# Draw for wearer to see +ugfx.orientation(270) +# Title +ugfx.set_default_font(ugfx.FONT_TITLE) +# info + +ugfx.set_default_font(ugfx.FONT_SMALL) +status = ugfx.Label(0, ugfx.height() - 30, ugfx.width(), status_height, "", justification=ugfx.Label.CENTER) +status.text('BATTERY INCOMING') + +# update loop +while True: + text = ""; + + if math.fmod(tick, 100) == 0: + value_wifi_strength = wifi_strength() + value_battery = battery() + if value_wifi_strength: + text += "Wi-Fi: %s%%, " % int(value_wifi_strength) + if value_battery: + text += "Battery: %s%%" % int(value_battery) + status.text(text) + tick +=1 + + # if intro_position_left > -intro_width: + # intro_position_left -= 1 + # intro_object.x( + # intro_position_left + # ) + # else: + # intro_object.x(0) + # intro_position_left = 0 + + ledChange() + + sleep_or_exit(0.05) diff --git a/home_default/main.py b/home_default/main.py index e358791..263aa4d 100644 --- a/home_default/main.py +++ b/home_default/main.py @@ -5,7 +5,7 @@ It gets automatically installed when a badge is newly activated or reset. """ -___name___ = "Homescreen (Default)" +___title___ = "Homescreen (Default)" ___license___ = "MIT" ___categories___ = ["Homescreens"] ___dependencies___ = ["homescreen", "shared/logo.png", "shared/sponsors.png"] diff --git a/home_ham/main.py b/home_ham/main.py index 69dfadb..e471545 100644 --- a/home_ham/main.py +++ b/home_ham/main.py @@ -3,7 +3,7 @@ This is a modified version of the default homescreen that allows you to set a callsign """ -___name___ = "Amateur Radio Homescreen" +___title___ = "Amateur Radio Homescreen" ___license___ = "MIT" ___categories___ = ["Homescreens"] ___dependencies___ = ["homescreen"] diff --git a/home_pycon/main.py b/home_pycon/main.py new file mode 100644 index 0000000..bad3158 --- /dev/null +++ b/home_pycon/main.py @@ -0,0 +1,93 @@ +"""Default homescreen + +This is the default homescreen for the Tilda Mk4. +It gets automatically installed when a badge is +newly activated or reset. +""" + +___title___ = "Homescreen (PyCon)" +___license___ = "MIT" +___categories___ = ["Homescreens"] +___dependencies___ = ["homescreen"] +___launchable___ = False +___bootstrapped___ = True + +import ugfx +from homescreen import * +import time +from tilda import Buttons + +init() + +# Padding for name +intro_height = 30 +intro_text = "Hi! I'm" +name_height = 60 +status_height = 20 +info_height = 30 +logo_path = "home_pycon/python_single.png" +logo_height = 82 +logo_width = 55 + +# Maximum length of name before downscaling +max_name = 8 + +# Background stuff +bg_color = 0xfecb2f +ugfx.clear(ugfx.html_color(bg_color)) + +# Colour stuff +style = ugfx.Style() +style.set_enabled([ugfx.BLACK, ugfx.html_color(bg_color), ugfx.html_color(bg_color), ugfx.html_color(bg_color)]) +style.set_background(ugfx.html_color(bg_color)) +ugfx.set_default_style(style) + +# Draw for people to see +ugfx.orientation(90) + +# Logo stuff +ugfx.display_image( + int((ugfx.width() - logo_width) / 2), + int((ugfx.height() - logo_height) / 2), + logo_path +) + + + + +# Draw introduction +ugfx.set_default_font(ugfx.FONT_TITLE) +ugfx.Label(0, ugfx.height() - name_height - intro_height, ugfx.width(), intro_height, intro_text, justification=ugfx.Label.CENTER) +# Process name +name_setting = name("Set your name in the settings app") +if len(name_setting) <= max_name: + ugfx.set_default_font(ugfx.FONT_NAME) +else: + ugfx.set_default_font(ugfx.FONT_MEDIUM_BOLD) +# Draw name +ugfx.Label(0, ugfx.height() - name_height, ugfx.width(), name_height, name_setting, justification=ugfx.Label.CENTER) + + + +# Draw for wearer to see +ugfx.orientation(270) +# Title +ugfx.set_default_font(ugfx.FONT_TITLE) +ugfx.Label(0, ugfx.height() - info_height * 2, ugfx.width(), info_height, "TiLDA Mk4", justification=ugfx.Label.CENTER) +# info +ugfx.Label(0, ugfx.height() - info_height, ugfx.width(), info_height, "Long Press MENU", justification=ugfx.Label.CENTER) + +ugfx.set_default_font(ugfx.FONT_SMALL) +status = ugfx.Label(0, ugfx.height() - info_height * 2 - status_height, ugfx.width(), status_height, "", justification=ugfx.Label.CENTER) + +# update loop +while True: + text = ""; + value_wifi_strength = wifi_strength() + value_battery = battery() + if value_wifi_strength: + text += "Wi-Fi: %s%%, " % int(value_wifi_strength) + if value_battery: + text += "Battery: %s%%" % int(value_battery) + status.text(text) + sleep_or_exit(0.5) diff --git a/home_pycon/python_single.png b/home_pycon/python_single.png new file mode 100644 index 0000000..1eaae1c Binary files /dev/null and b/home_pycon/python_single.png differ diff --git a/home_stratum0/main.py b/home_stratum0/main.py index c39f74b..053fbbd 100644 --- a/home_stratum0/main.py +++ b/home_stratum0/main.py @@ -3,7 +3,7 @@ This is the Stratum 0 flavored homescreen for the Tilda Mk4. """ -___name___ = "Homescreen (Stratum 0)" +___title___ = "Homescreen (Stratum 0)" ___license___ = "MIT" ___categories___ = ["Homescreens"] ___dependencies___ = ["homescreen"] diff --git a/home_trans/main.py b/home_trans/main.py index 1b2bead..fe3fcbe 100644 --- a/home_trans/main.py +++ b/home_trans/main.py @@ -5,7 +5,7 @@ Press 0 to go back to normal or 8 to show the flag. Hold * to activate all LEDs for use as a torch. """ -___name___ = "Homescreen (Trans)" +___title___ = "Homescreen (Trans)" ___license___ = "MIT" ___categories___ = ["Homescreens"] ___dependencies___ = ["homescreen", "shared/logo.png"] diff --git a/launcher/main.py b/launcher/main.py index 1fa4585..d2498a1 100644 --- a/launcher/main.py +++ b/launcher/main.py @@ -1,8 +1,8 @@ """Launcher for apps currently installed""" -___name___ = "Launcher" +___title___ = "Launcher" ___license___ = "MIT" -___categories___ = ["System"] +___categories___ = ["System", "Launcher"] ___dependencies___ = ["dialogs", "app", "ugfx_helper"] ___launchable___ = False ___bootstrapped___ = True diff --git a/lib/badge_store.py b/lib/badge_store.py index 4be17c6..2eaa026 100644 --- a/lib/badge_store.py +++ b/lib/badge_store.py @@ -34,6 +34,9 @@ class BadgeStore: def install(self, apps): return self._create_installers(self._call("install", {"apps": ",".join(apps)})) + def update(self, apps): + return self._create_installers(self._call("update", {"apps": ",".join(apps)})) + def bootstrap(self): return self._create_installers(self._call("bootstrap")) diff --git a/lib/database.py b/lib/database.py index 55d7871..0d30169 100644 --- a/lib/database.py +++ b/lib/database.py @@ -6,7 +6,7 @@ Values can be anything json can store, including a dict Usage: import database -with database.open() as db: +with database.Database() as db: print(db.get("hello", "default")) db.set("foo", "world") db.delete("bar") diff --git a/lib/dialogs.py b/lib/dialogs.py index 86626b0..9599aec 100644 --- a/lib/dialogs.py +++ b/lib/dialogs.py @@ -41,18 +41,30 @@ def prompt_boolean(text, title="TiLDA", true_text="Yes", false_text="No", font=F window = ugfx.Container(5, 5, width, height) window.show() ugfx.set_default_font(font) - window.text(5, 10, title, TILDA_COLOR) - window.line(0, 30, width, 30, ugfx.BLACK) + window.text(5, 5, title, TILDA_COLOR) + window.line(0, 25, width, 25, ugfx.BLACK) if false_text: true_text = "A: " + true_text false_text = "B: " + false_text ugfx.set_default_font(font) - label = ugfx.Label(5, 30, width - 10, height - 80, text = text, parent=window) + label = ugfx.Label(5, 30, width - 10, height - 80, text = text, parent=window, justification=4) + ugfx.set_default_font(FONT_MEDIUM_BOLD) - button_yes = ugfx.Button(5, height - 40, width // 2 - 15 if false_text else width - 15, 30 , true_text, parent=window) - button_no = ugfx.Button(width // 2 + 5, height - 40, width // 2 - 15, 30 , false_text, parent=window) if false_text else None + button_yes = ugfx.Button(5, height - 40, width // 2 - 10 if false_text else width - 15, 30 , true_text, parent=window) + button_no = ugfx.Button(width // 2, height - 40, width // 2 - 10, 30 , false_text, parent=window) if false_text else None + + # Find newlines in label text to scroll. + def find_all(a_str, sub): + start = 0 + while True: + start = a_str.find(sub, start) + if start == -1: return + yield start + 1 # Trap: \n becomes a single character, not 2. + start += len(sub) # use start += 1 to find overlapping matches + new_line_pos = [0] + list(find_all(text, '\n')) + text_scroll_offset = 0 try: #button_yes.attach_input(ugfx.BTN_A,0) # todo: re-enable once working @@ -64,6 +76,16 @@ def prompt_boolean(text, title="TiLDA", true_text="Yes", false_text="No", font=F sleep.wfi() if buttons.is_triggered(buttons.Buttons.BTN_A): return True if buttons.is_triggered(buttons.Buttons.BTN_B): return False + # Allow scrolling by new lines. + if buttons.is_triggered(buttons.Buttons.JOY_Down): + if text_scroll_offset < len(new_line_pos)-1: + text_scroll_offset = text_scroll_offset + 1 + label.text(text[new_line_pos[text_scroll_offset]:]) + + if buttons.is_triggered(buttons.Buttons.JOY_Up): + if (text_scroll_offset > 0): + text_scroll_offset=text_scroll_offset - 1 + label.text(text[new_line_pos[text_scroll_offset]:]) finally: window.hide() @@ -129,7 +151,7 @@ def handle_keypad(edit, numeric): buttons.Buttons.BTN_6: ["m", "n", "o", "6"], buttons.Buttons.BTN_7: ["p", "q", "r", "s", "7"], buttons.Buttons.BTN_8: ["t", "u", "v", "8"], - buttons.Buttons.BTN_9: ["w", "x", "y", "9"], + buttons.Buttons.BTN_9: ["w", "x", "y", "z", "9"], buttons.Buttons.BTN_Hash: ["#"], buttons.Buttons.BTN_Star: ["*", "+"], } @@ -167,9 +189,10 @@ def prompt_option(options, index=0, text = None, title=None, select_text="OK", n window = ugfx.Container(5, 5, ugfx.width() - 10, ugfx.height() - 10) window.show() + list_y = 30 if title: - window.text(5, 10, title, TILDA_COLOR) + window.text(5, 5, title, TILDA_COLOR) window.line(0, 25, ugfx.width() - 10, 25, ugfx.BLACK) list_y = 30 if text: @@ -179,14 +202,23 @@ def prompt_option(options, index=0, text = None, title=None, select_text="OK", n else: window.text(5, 10, text, ugfx.BLACK) - options_list = ugfx.List(5, list_y, ugfx.width() - 25, 260 - list_y, parent = window) + options_list = ugfx.List(5, list_y, ugfx.width() - 24, 265 - list_y, parent = window) options_list.disable_draw() + optnum = 1 for option in options: if isinstance(option, dict) and option["title"]: - options_list.add_item(option["title"]) + title = option["title"] else: - options_list.add_item(str(option)) + title = str(option) + + if optnum < 11: + # mod 10 to make 10th item numbered 0 + options_list.add_item("{}: {}".format((optnum % 10),title)) + else: + options_list.add_item(" {}".format(title)) + optnum = optnum + 1 + options_list.enable_draw() options_list.selected_index(index) @@ -195,7 +227,7 @@ def prompt_option(options, index=0, text = None, title=None, select_text="OK", n none_text = "B: " + none_text button_select = ugfx.Button(5, ugfx.height() - 50, 105 if none_text else 200, 30 , select_text, parent=window) - button_none = ugfx.Button(117, ugfx.height() - 50, 105, 30 , none_text, parent=window) if none_text else None + button_none = ugfx.Button(116, ugfx.height() - 50, 105, 30 , none_text, parent=window) if none_text else None try: while True: @@ -260,9 +292,9 @@ class WaitingMessage: def __init__(self, text="Please Wait...", title="TiLDA"): self.window = ugfx.Container(30, 30, ugfx.width() - 60, ugfx.height() - 60) self.window.show() - self.window.text(5, 10, title, TILDA_COLOR) - self.window.line(0, 30, ugfx.width() - 60, 30, ugfx.BLACK) - self.label = ugfx.Label(5, 40, self.window.width() - 10, ugfx.height() - 40, text = text, parent=self.window) + self.window.text(5, 5, title, TILDA_COLOR) + self.window.line(0, 25, ugfx.width() - 60, 25, ugfx.BLACK) + self.label = ugfx.Label(5, 40, self.window.width() - 15, ugfx.height() - 40, text = text, parent=self.window) # Indicator to show something is going on #self.indicator = ugfx.Label(ugfx.width() - 100, 0, 20, 20, text = "...", parent=self.window) diff --git a/lib/homescreen.py b/lib/homescreen.py index 8702735..19bf903 100644 --- a/lib/homescreen.py +++ b/lib/homescreen.py @@ -17,7 +17,7 @@ They also *may*: """ ___license___ = "MIT" -___dependencies___ = ["database", "buttons", "random", "app", "sleep", "ugfx_helper", "wifi", "sim800"] +___dependencies___ = ["database", "buttons", "app", "sleep", "ugfx_helper", "wifi", "sim800"] import database, ugfx, random, buttons, tilda, sleep, ugfx_helper, wifi, time, sim800 from app import App @@ -51,7 +51,13 @@ def sleep_or_exit(interval = 0.5): # todo: do this better - check button multiple times and sleep for only a short while if buttons.is_triggered(tilda.Buttons.BTN_Menu): clean_up() - App("launcher").boot() + launcher = "launcher" + try: + with open("default_launcher.txt", "r") as dl: + launcher=dl.readline() + except OSError: + pass + App(launcher).boot() sleep.sleep(interval) diff --git a/lib/ntp.py b/lib/ntp.py index 1c0fbdf..702ad41 100644 --- a/lib/ntp.py +++ b/lib/ntp.py @@ -12,7 +12,7 @@ import machine # (date(2000, 1, 1) - date(1900, 1, 1)).days * 24*60*60 NTP_DELTA = 3155673600 # With Mk3 Firmware an IP address string works 5%, hangs at socket.socket(..) 95%, could be a bug in 2016 upython? -NTP_HOSTS = ["ntp-gps.emf.camp", "0.emfbadge.pool.ntp.org", "1.emfbadge.pool.ntp.org", "2.emfbadge.pool.ntp.org", "3.emfbadge.pool.ntp.org"] +NTP_HOSTS = ["0.emfbadge.pool.ntp.org", "1.emfbadge.pool.ntp.org", "2.emfbadge.pool.ntp.org", "3.emfbadge.pool.ntp.org"] NTP_PORT = 123 def get_NTP_time(): diff --git a/lib/random.py b/lib/random.py deleted file mode 100644 index d072333..0000000 --- a/lib/random.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Library to generate random numbers - -Warning! Don't use this for anything important, it's probably biased -""" - -___license___ = "MIT" - -# todo: simplify this by using "urandom" -import os - -_bigrand_bytes = 10 -_bigrand_max = pow(256, _bigrand_bytes) - -def _bigrand(): - """generates a random number between 0 (incl) and _bigrand_max (excl)""" - base = 0 - for b in os.urandom(_bigrand_bytes): - base = (base << 8) + b - return base - -def random(): - """Return the next random floating point number in the range [0.0, 1.0).""" - return _bigrand() / _bigrand_max - -def randrange(start, stop=None): - """Return a randomly selected element from range(start, stop)""" - if stop is None: - stop = start - start = 0 - return start + (_bigrand() * (stop - start) // _bigrand_max) - -def randint(start, stop): - """Return a random integer N such that a <= N <= b.""" - return randrange(start, stop + 1) - -def shuffle(seq): - """Shuffle the sequence x in place.""" - l = len(seq) - for i in range(l): - j = randrange(l) - seq[i], seq[j] = seq[j], seq[i] diff --git a/lib/sim800.py b/lib/sim800.py index ebd3da5..cb5c53a 100644 --- a/lib/sim800.py +++ b/lib/sim800.py @@ -25,6 +25,7 @@ dirtybuffer = False # Flag if the buffer could have residual end of reresponsesp # A list of callback functions callbacks = [] +server_callback = None # Globals for remembering callback data clip = "" @@ -105,6 +106,12 @@ def processcallbacks(line): # Check for Bluetooth pairing request if line.startswith("+BTPAIRING:"): btpairing = line[11:].strip() + # Handle TCP Server Data + if line.startswith("+RECEIVE"): + dlen = int(line.split(",")[2].rstrip(":"))+1 + payload = uart.read(dlen) + if server_callback: + micropython.schedule(server_callback, payload[1:]) # Check for app callbacks for entry in callbacks: if line.startswith(entry[0]): @@ -847,6 +854,28 @@ def callbuttonpressed_internal(nullparam=None): def endbuttonpressed_internal(nullparam=None): hangup() +#GPRS and TCP server functions + +def setup_gprs(): + command("AT+CIPSHUT", response_timeout=60000, custom_endofdata="SHUT OK") + command("AT+CGATT?", response_timeout=10000) + command("AT+CIPMUX=1", response_timeout=10000) + +def connect_gprs(apn): + command("AT+CSTT=\""+apn+"\"", response_timeout=10000) + command("AT+CIICR", response_timeout=10000) + command("AT+CIFSR") + +def stop_gprs(): + command("AT+CIPSHUT", response_timeout=60000, custom_endofdata="SHUT OK") + +def start_server(port, callback): + global server_callback + server_callback = callback + command("AT+CIPSERVER=1,"+str(port), response_timeout=10000) + +def stop_server(): + command("AT+CIPSERVER=0", response_timeout=10000) # Startup... diff --git a/lib/test_random.py b/lib/test_random.py index 5402caa..6c60dad 100644 --- a/lib/test_random.py +++ b/lib/test_random.py @@ -1,7 +1,7 @@ """Tests for random lib""" ___license___ = "MIT" -___dependencies___ = ["upip:unittest", "random"] +___dependencies___ = ["upip:unittest"] import unittest from random import * diff --git a/lobstervision/main.py b/lobstervision/main.py index 947132c..811c9ce 100644 --- a/lobstervision/main.py +++ b/lobstervision/main.py @@ -1,6 +1,6 @@ """View images from the EMF 2018 time-lapse camera """ -___name___ = "Lobster Vision" +___title___ = "Lobster Vision" ___license___ = "MIT" ___dependencies___ = ["app", "dialogs", "wifi", "buttons", "http", "ugfx_helper"] ___categories___ = ["Other"] diff --git a/lucky_melody_machine/main.py b/lucky_melody_machine/main.py index a98e1b1..6200ab1 100644 --- a/lucky_melody_machine/main.py +++ b/lucky_melody_machine/main.py @@ -2,7 +2,7 @@ Learn your personal lucky melody. """ -___name___ = "lucky_melody_machine" +___title___ = "Lucky Melody Machine" ___license___ = "WTFPL" ___dependencies___ = ["app", "buttons", "dialogs", "speaker", "sleep", "ugfx_helper"] ___categories___ = ["Sound"] diff --git a/mario/main.py b/mario/main.py index 2dcd37d..92d8173 100644 --- a/mario/main.py +++ b/mario/main.py @@ -5,7 +5,7 @@ Gracefully reboot into main menu on Menu Press. Replay Track when user pushes a button. """ -___name___ = "Mario Theme" +___title___ = "Mario Theme" ___license___ = "" ___categories___ = ["Sound"] ___dependencies___ = ["speaker", "buttons", "ugfx_helper", "app", "wifi", "http", "sleep" ] diff --git a/mass_storage/main.py b/mass_storage/main.py index aa88772..8d76e28 100644 --- a/mass_storage/main.py +++ b/mass_storage/main.py @@ -1,6 +1,6 @@ """Enables mass storage mode in a safe way""" -___name___ = "Mass Storage Enabler" +___title___ = "Mass Storage Enabler" ___license___ = "MIT" ___dependencies___ = ["dialogs", "ugfx_helper"] ___categories___ = ["EMF"] @@ -11,12 +11,18 @@ import ugfx, tilda, ugfx_helper, dialogs, app, time ugfx_helper.init() ugfx.clear() -print("enabling USB storage...") -tilda.storage_enable_usb() -time.sleep(1) -print("DONE") -with dialogs.WaitingMessage(title="Mass Storage Enabled", text="You can now use the badge like a USB key.\nPlease safely eject afterwards. This app will close automatically."): - print("Waiting for USB mass storage to be unmounted...") - tilda.storage_disable_usb() +user_agreed = dialogs.prompt_boolean("Note: enabling mass storage is slightly risky, as the badge may end up factory " + "resetting even if you safely eject it. Do you want to continue?") + +if user_agreed: + print("enabling USB storage...") + tilda.storage_enable_usb() + time.sleep(1) print("DONE") + with dialogs.WaitingMessage(title="Mass Storage Enabled", text="You can now use the badge like a USB key.\nPlease safely eject afterwards. This app will close automatically."): + print("Waiting for USB mass storage to be unmounted...") + tilda.storage_disable_usb() + print("DONE") + app.restart_to_default() +else: app.restart_to_default() diff --git a/memobadge/main.py b/memobadge/main.py index fe79a11..c797f72 100644 --- a/memobadge/main.py +++ b/memobadge/main.py @@ -1,6 +1,6 @@ """This app tests all the onboard sensors and system info""" -___name___ = "Memobadge" +___title___ = "Memobadge" ___license___ = "MIT" ___dependencies___ = ["app", "sim800", "sleep", "ugfx_helper"] ___categories___ = ["Sound"] diff --git a/nyan/main.py b/nyan/main.py new file mode 100644 index 0000000..3efca5c --- /dev/null +++ b/nyan/main.py @@ -0,0 +1,39 @@ +"""Nyan Cat Animation! Rotate the screen with 'A'.""" + +___name___ = "nyan" +___license___ = "MIT" +___dependencies___ = ["sleep", "app", "ugfx_helper", + "shared/nyan/0.png", + "shared/nyan/1.png", + "shared/nyan/2.png", + "shared/nyan/3.png", + "shared/nyan/4.png", + "shared/nyan/5.png"] + +___categories___ = ["Homescreens", "Other"] + +import ugfx_helper, os, wifi, ugfx, http, time, sleep, app +from tilda import Buttons + +# initialize screen +ugfx_helper.init() +ugfx.clear(ugfx.BLACK) + +ugfx.backlight(100) + +n = 0 +r = 270 +while True: + ugfx.display_image( 0, 90, "shared/nyan/{}.png".format(n) ) + n = (n+1) % 6 + sleep.sleep_ms(10) + + if Buttons.is_pressed(Buttons.BTN_B): + break + elif Buttons.is_pressed(Buttons.BTN_A): + r = (r + 180) % 360 + ugfx.clear(ugfx.BLACK) + ugfx.orientation(r) + +ugfx.clear() +app.restart_to_default() \ No newline at end of file diff --git a/nyan_home/frame_0_delay-0.07s.gif b/nyan_home/frame_0_delay-0.07s.gif new file mode 100644 index 0000000..3cd1b49 Binary files /dev/null and b/nyan_home/frame_0_delay-0.07s.gif differ diff --git a/nyan_home/frame_10_delay-0.07s.gif b/nyan_home/frame_10_delay-0.07s.gif new file mode 100644 index 0000000..9bd6bb7 Binary files /dev/null and b/nyan_home/frame_10_delay-0.07s.gif differ diff --git a/nyan_home/frame_11_delay-0.07s.gif b/nyan_home/frame_11_delay-0.07s.gif new file mode 100644 index 0000000..1ae2a33 Binary files /dev/null and b/nyan_home/frame_11_delay-0.07s.gif differ diff --git a/nyan_home/frame_1_delay-0.07s.gif b/nyan_home/frame_1_delay-0.07s.gif new file mode 100644 index 0000000..c241da5 Binary files /dev/null and b/nyan_home/frame_1_delay-0.07s.gif differ diff --git a/nyan_home/frame_2_delay-0.07s.gif b/nyan_home/frame_2_delay-0.07s.gif new file mode 100644 index 0000000..9915db3 Binary files /dev/null and b/nyan_home/frame_2_delay-0.07s.gif differ diff --git a/nyan_home/frame_3_delay-0.07s.gif b/nyan_home/frame_3_delay-0.07s.gif new file mode 100644 index 0000000..5671518 Binary files /dev/null and b/nyan_home/frame_3_delay-0.07s.gif differ diff --git a/nyan_home/frame_4_delay-0.07s.gif b/nyan_home/frame_4_delay-0.07s.gif new file mode 100644 index 0000000..5f6d550 Binary files /dev/null and b/nyan_home/frame_4_delay-0.07s.gif differ diff --git a/nyan_home/frame_5_delay-0.07s.gif b/nyan_home/frame_5_delay-0.07s.gif new file mode 100644 index 0000000..be68cdd Binary files /dev/null and b/nyan_home/frame_5_delay-0.07s.gif differ diff --git a/nyan_home/frame_6_delay-0.07s.gif b/nyan_home/frame_6_delay-0.07s.gif new file mode 100644 index 0000000..15d21d0 Binary files /dev/null and b/nyan_home/frame_6_delay-0.07s.gif differ diff --git a/nyan_home/frame_7_delay-0.07s.gif b/nyan_home/frame_7_delay-0.07s.gif new file mode 100644 index 0000000..7674493 Binary files /dev/null and b/nyan_home/frame_7_delay-0.07s.gif differ diff --git a/nyan_home/frame_8_delay-0.07s.gif b/nyan_home/frame_8_delay-0.07s.gif new file mode 100644 index 0000000..ef75ebf Binary files /dev/null and b/nyan_home/frame_8_delay-0.07s.gif differ diff --git a/nyan_home/frame_9_delay-0.07s.gif b/nyan_home/frame_9_delay-0.07s.gif new file mode 100644 index 0000000..71aa5a0 Binary files /dev/null and b/nyan_home/frame_9_delay-0.07s.gif differ diff --git a/nyan_home/main.py b/nyan_home/main.py new file mode 100644 index 0000000..f52d216 --- /dev/null +++ b/nyan_home/main.py @@ -0,0 +1,80 @@ +"""Nyan cat +""" +___name___ = "Nyan" +___license___ = "MIT" +___dependencies___ = ["app", "homescreen", "ugfx_helper"] +___categories___ = ["Homescreens"] +___bootstrapped___ = False + +from app import * +import ugfx +from homescreen import * +import ugfx_helper +import machine + +from tilda import Buttons +from machine import Neopix + +ext = False +bkl = False + +intro_text = "Hi! I'm" +name_height = 70 +intro_height = 30 +max_name = 8 + +def cbButtonA(button_id): + global bkl + bkl = False + +def cbButtonB(button_id): + global ext + ext = True + +frame = 0 + +def force_backlight(): + if ugfx.backlight() == 0: + ugfx.power_mode(ugfx.POWER_ON) + ugfx.backlight(100) + +ugfx_helper.init() +ugfx.clear() + +ugfx.orientation(180) +force_backlight() + + + +#everything from here onwards is unknown +# Colour stuff +style = ugfx.Style() +style.set_enabled([ugfx.WHITE,ugfx.html_color(0x003265),ugfx.html_color(0x003265),ugfx.html_color(0x003265)]) +style.set_background(ugfx.html_color(0x003265)) +ugfx.set_default_style(style) +ugfx.orientation(90) + +# Draw for people to see +ugfx.orientation(90) +# Draw introduction +ugfx.set_default_font(ugfx.FONT_TITLE) +ugfx.Label(0, ugfx.height() - name_height - intro_height, ugfx.width(), intro_height, intro_text, justification=ugfx.Label.CENTER) +# Process name +name_setting = name("Set your name in the settings app") +if len(name_setting) <= max_name: + ugfx.set_default_font(ugfx.FONT_NAME) +else: + ugfx.set_default_font(ugfx.FONT_MEDIUM_BOLD) +# Draw name +ugfx.Label(0, ugfx.height() - name_height, ugfx.width(), name_height, name_setting, justification=ugfx.Label.CENTER) + +i = 0 +while True: + strimage = 'nyan/frame_'+str(i)+'_delay-0.07s.gif' + ugfx.display_image(0, 0, strimage) + i = i + 1 + if i > 11: + i = 0 + sleep_or_exit(0.5) + +app.restart_to_default() \ No newline at end of file diff --git a/orbs/main.py b/orbs/main.py new file mode 100644 index 0000000..8b52d9a --- /dev/null +++ b/orbs/main.py @@ -0,0 +1,145 @@ +""" +Orbs Game: Set your name and see the scores +""" +___name___ = "Orbs Game" +___license___ = "MIT" +___dependencies___ = ["app", "dialogs", "sim800", "ugfx_helper"] +___categories___ = ["Games"] +___bootstrapped___ = True + +from app import * +from dialogs import * +import utime +import ugfx +import ugfx_helper +from orbs.umqtt.simple import MQTTClient +import network +from machine import mem32 +import wifi +wifi.connect() + +ugfx_helper.init() +ugfx.clear() +broker='151.216.207.139' +mqtt_username='orbs' +mqtt_password='orbs123' +scoretext="" + +MACAddress=str(mem32[0x400fef20]) + str(mem32[0x400fef24]) + str(mem32[0x400fef28]) + str(mem32[0x400fef2C]) +regOK=False +regFAILED=False + +def mqtt_connect(): + client = MQTTClient(MACAddress,broker, user=mqtt_username, password=mqtt_password) + client.set_callback(sub_cb) + connected=False + try: + client.connect() + connected=True + except Exception as err: + connected=False + if (connected): + return client + else: + return False + +def sub_cb(topic,msg): + global regOK + global regFAILED + global scoretext + try: + (t1,t2,t3,targetBadge,messageType)=topic.decode('utf-8').split('/') + strmsg=msg.decode('utf-8') + if messageType=="regok": + regOK=True + if messageType=="regerror": + regFAILED=True + if messageType=="scores": + scoretext=msg + except: + return + + +def update_token(token): + lb=open("token.txt","w") + lb.write(token) + lb.close() + +def do_gettoken(): + notice("Enter your RFID token ID digits only. Get it right!", title="Orbs Game") + token=prompt_text("Enter token:") + if len(token)==8 or len(token)==14 or len(token)==20: + return token + else: + notice("Invalid token", title="Orbs Game") + return "" + +def do_register(client): + playername=prompt_text("Enter name:") + playername=playername.replace(",",".") + regOK==False + regFAILED==False + if len(playername)>3: + client.publish("/registration/from/" + MACAddress + "/name",mytoken + "," + playername) + notice("Name request sent") + client.check_msg() + if regOK==True: + notice("Registration completed") + if regFAILED==True: + notice("Registration failed") + else: + notice("Name too short") + +def get_token(): + try: + lb=open("token.txt","r") + token=lb.readline() + lb.close() + if token=="": + token=do_gettoken() + except: + token="" + if token=="": + token=do_gettoken() + return token + +def do_showscores(client): + client.publish("/registration/from/" + MACAddress + "/score",mytoken) + notice("Requested scores") + client.check_msg() + if len(scoretext)>0: + (playername,playerscore,rank,redscore,greenscore,bluescore)=scoretext.decode('utf-8').split(',') + notice("Player: " + playername + chr(13) + "Score: " + playerscore + chr(13) + "Rank: " + rank) + notice("Red Score: " + redscore + chr(13) + "Green Score: " + greenscore + chr(13) + "Blue Score: " + bluescore) + else: + notice("Failed to get scores") + +mqttclient=mqtt_connect() +while (not mqttclient): + utime.sleep(2) + mqttclient=mqtt_connect() +mqttclient.subscribe(topic='/badge/to/' + MACAddress + '/#') +#mqttclient.subscribe(topic='/scoreboard/to/all/#') +mytoken=get_token() +if len(mytoken)==0: + notice("Token required",title="Orbs Game") + try: + mqttclient.close() + except: + restart_to_default() + restart_to_default() +update_token(mytoken) + + +menuset = [] +menuset.append({ "title" : "Register", "index" : 1 }) +menuset.append({ "title" : "Scores", "index" : 2 }) + +while True: + selection = prompt_option(menuset, text="Orbs Game", select_text="Select", none_text="Exit") + if (not selection): + restart_to_default() + elif (selection["index"]==1): + do_register(mqttclient) + elif (selection["index"]==2): + do_showscores(mqttclient) diff --git a/orbs/umqtt/robust.py b/orbs/umqtt/robust.py new file mode 100644 index 0000000..7ee40e0 --- /dev/null +++ b/orbs/umqtt/robust.py @@ -0,0 +1,43 @@ +import utime +from . import simple + +class MQTTClient(simple.MQTTClient): + + DELAY = 2 + DEBUG = False + + def delay(self, i): + utime.sleep(self.DELAY) + + def log(self, in_reconnect, e): + if self.DEBUG: + if in_reconnect: + print("mqtt reconnect: %r" % e) + else: + print("mqtt: %r" % e) + + def reconnect(self): + i = 0 + while 1: + try: + return super().connect(False) + except OSError as e: + self.log(True, e) + i += 1 + self.delay(i) + + def publish(self, topic, msg, retain=False, qos=0): + while 1: + try: + return super().publish(topic, msg, retain, qos) + except OSError as e: + self.log(False, e) + self.reconnect() + + def wait_msg(self): + while 1: + try: + return super().wait_msg() + except OSError as e: + self.log(False, e) + self.reconnect() diff --git a/orbs/umqtt/simple.py b/orbs/umqtt/simple.py new file mode 100644 index 0000000..8216fa5 --- /dev/null +++ b/orbs/umqtt/simple.py @@ -0,0 +1,204 @@ +import usocket as socket +import ustruct as struct +from ubinascii import hexlify + +class MQTTException(Exception): + pass + +class MQTTClient: + + def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0, + ssl=False, ssl_params={}): + if port == 0: + port = 8883 if ssl else 1883 + self.client_id = client_id + self.sock = None + self.server = server + self.port = port + self.ssl = ssl + self.ssl_params = ssl_params + self.pid = 0 + self.cb = None + self.user = user + self.pswd = password + self.keepalive = keepalive + self.lw_topic = None + self.lw_msg = None + self.lw_qos = 0 + self.lw_retain = False + + def _send_str(self, s): + self.sock.write(struct.pack("!H", len(s))) + self.sock.write(s) + + def _recv_len(self): + n = 0 + sh = 0 + while 1: + b = self.sock.read(1)[0] + n |= (b & 0x7f) << sh + if not b & 0x80: + return n + sh += 7 + + def set_callback(self, f): + self.cb = f + + def set_last_will(self, topic, msg, retain=False, qos=0): + assert 0 <= qos <= 2 + assert topic + self.lw_topic = topic + self.lw_msg = msg + self.lw_qos = qos + self.lw_retain = retain + + def connect(self, clean_session=True): + self.sock = socket.socket() + addr = socket.getaddrinfo(self.server, self.port)[0][-1] + self.sock.connect(addr) + if self.ssl: + import ussl + self.sock = ussl.wrap_socket(self.sock, **self.ssl_params) + premsg = bytearray(b"\x10\0\0\0\0\0") + msg = bytearray(b"\x04MQTT\x04\x02\0\0") + + sz = 10 + 2 + len(self.client_id) + msg[6] = clean_session << 1 + if self.user is not None: + sz += 2 + len(self.user) + 2 + len(self.pswd) + msg[6] |= 0xC0 + if self.keepalive: + assert self.keepalive < 65536 + msg[7] |= self.keepalive >> 8 + msg[8] |= self.keepalive & 0x00FF + if self.lw_topic: + sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg) + msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3 + msg[6] |= self.lw_retain << 5 + + i = 1 + while sz > 0x7f: + premsg[i] = (sz & 0x7f) | 0x80 + sz >>= 7 + i += 1 + premsg[i] = sz + + self.sock.write(premsg, i + 2) + self.sock.write(msg) + #print(hex(len(msg)), hexlify(msg, ":")) + self._send_str(self.client_id) + if self.lw_topic: + self._send_str(self.lw_topic) + self._send_str(self.lw_msg) + if self.user is not None: + self._send_str(self.user) + self._send_str(self.pswd) + resp = self.sock.read(4) + assert resp[0] == 0x20 and resp[1] == 0x02 + if resp[3] != 0: + raise MQTTException(resp[3]) + return resp[2] & 1 + + def disconnect(self): + self.sock.write(b"\xe0\0") + self.sock.close() + + def ping(self): + self.sock.write(b"\xc0\0") + + def publish(self, topic, msg, retain=False, qos=0): + pkt = bytearray(b"\x30\0\0\0") + pkt[0] |= qos << 1 | retain + sz = 2 + len(topic) + len(msg) + if qos > 0: + sz += 2 + assert sz < 2097152 + i = 1 + while sz > 0x7f: + pkt[i] = (sz & 0x7f) | 0x80 + sz >>= 7 + i += 1 + pkt[i] = sz + #print(hex(len(pkt)), hexlify(pkt, ":")) + self.sock.write(pkt, i + 1) + self._send_str(topic) + if qos > 0: + self.pid += 1 + pid = self.pid + struct.pack_into("!H", pkt, 0, pid) + self.sock.write(pkt, 2) + self.sock.write(msg) + if qos == 1: + while 1: + op = self.wait_msg() + if op == 0x40: + sz = self.sock.read(1) + assert sz == b"\x02" + rcv_pid = self.sock.read(2) + rcv_pid = rcv_pid[0] << 8 | rcv_pid[1] + if pid == rcv_pid: + return + elif qos == 2: + assert 0 + + def subscribe(self, topic, qos=0): + assert self.cb is not None, "Subscribe callback is not set" + pkt = bytearray(b"\x82\0\0\0") + self.pid += 1 + struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid) + #print(hex(len(pkt)), hexlify(pkt, ":")) + self.sock.write(pkt) + self._send_str(topic) + self.sock.write(qos.to_bytes(1, "little")) + while 1: + op = self.wait_msg() + if op == 0x90: + resp = self.sock.read(4) + #print(resp) + assert resp[1] == pkt[2] and resp[2] == pkt[3] + if resp[3] == 0x80: + raise MQTTException(resp[3]) + return + + # Wait for a single incoming MQTT message and process it. + # Subscribed messages are delivered to a callback previously + # set by .set_callback() method. Other (internal) MQTT + # messages processed internally. + def wait_msg(self): + res = self.sock.read(1) + self.sock.setblocking(True) + if res is None: + return None + if res == b"": + raise OSError(-1) + if res == b"\xd0": # PINGRESP + sz = self.sock.read(1)[0] + assert sz == 0 + return None + op = res[0] + if op & 0xf0 != 0x30: + return op + sz = self._recv_len() + topic_len = self.sock.read(2) + topic_len = (topic_len[0] << 8) | topic_len[1] + topic = self.sock.read(topic_len) + sz -= topic_len + 2 + if op & 6: + pid = self.sock.read(2) + pid = pid[0] << 8 | pid[1] + sz -= 2 + msg = self.sock.read(sz) + self.cb(topic, msg) + if op & 6 == 2: + pkt = bytearray(b"\x40\x02\0\0") + struct.pack_into("!H", pkt, 2, pid) + self.sock.write(pkt) + elif op & 6 == 4: + assert 0 + + # Checks whether a pending message from server is available. + # If not, returns immediately with None. Otherwise, does + # the same processing as wait_msg. + def check_msg(self): + self.sock.setblocking(False) + return self.wait_msg() diff --git a/party/main.py b/party/main.py index b6c7687..708e5a6 100644 --- a/party/main.py +++ b/party/main.py @@ -2,7 +2,7 @@ ''' ___author___ = 'Skybound - ECS' -___name___ = 'Party' +___title___ = 'Party' ___license___ = 'MIT' ___categories___ = ['LEDs'] ___bootstrapped___ = False diff --git a/phone/main.py b/phone/main.py index d6087bf..137766b 100644 --- a/phone/main.py +++ b/phone/main.py @@ -1,6 +1,6 @@ """Phone app for baic calling functions """ -___name___ = "Phone" +___title___ = "Phone" ___license___ = "MIT" ___dependencies___ = ["app", "dialogs", "sim800", "ugfx_helper"] ___categories___ = ["System"] diff --git a/pong/main.py b/pong/main.py new file mode 100644 index 0000000..a960adf --- /dev/null +++ b/pong/main.py @@ -0,0 +1,220 @@ +"""Pong!""" + +___name___ = "Pong" +___license___ = "WTFPL" +___categories___ = ["Games"] +___dependencies___ = ["dialogs", "app", "ugfx_helper", "sleep", "buttons"] + +import math, ugfx, ugfx_helper, random, sleep, buttons, time +from tilda import Buttons + +ugfx_helper.init() + +SCREEN_WIDTH = ugfx.width() +SCREEN_HEIGHT = ugfx.height() + +bgColor = ugfx.BLACK +ballColor = ugfx.html_color(0x00FF00) +paddleColor = ugfx.html_color(0x00FF00) +netColor = ugfx.html_color(0x00FF00) + +class Paddle(): + height = 6 + width = 60 + + moveSpeed = 4 + + needsRedraw = True + + def __init__(self, type): + self.type = type + + self.x = SCREEN_WIDTH/2 + self.previousX = self.x + + if type == 0: + self.y = self.height/2 + else: + self.y = SCREEN_HEIGHT - (self.height/2) + + def draw(self): + if self.needsRedraw: + ugfx.area(int(self.previousX-self.width/2),int(self.y-self.height/2),int(self.width),int(self.height),bgColor) + self.needsRedraw = False + + ugfx.area(int(self.x-self.width/2),int(self.y-self.height/2),int(self.width),int(self.height),paddleColor) + + def update(self): + if self.type == 1: + if Buttons.is_pressed(Buttons.BTN_Hash): + self.needsRedraw = True + self.previousX = self.x + self.x += self.moveSpeed + + if Buttons.is_pressed(Buttons.BTN_Star): + self.needsRedraw = True + self.previousX = self.x + self.x -= self.moveSpeed + if self.type == 0: + if Buttons.is_pressed(Buttons.BTN_3): + self.needsRedraw = True + self.previousX = self.x + self.x += self.moveSpeed + if Buttons.is_pressed(Buttons.BTN_1): + self.needsRedraw = True + self.previousX = self.x + self.x -= self.moveSpeed + + if self.x + self.width/2 > SCREEN_WIDTH: + self.x = SCREEN_WIDTH - self.width/2 + + if self.x -self.width/2 < 0: + self.x = self.width/2 + +class Ball(): + size = 10 + + x = 0 + y = 0 + + yDeathOffset = 5+3 + + def __init__(self): + self.x = random.randint(30,SCREEN_WIDTH-30) + + self.y = SCREEN_HEIGHT/2 + + self.vX = 3 + + if random.randrange(2) == 1: + self.vY = 3 + else: + self.vY = -3 + + self.previousX = self.x + self.previousY = self.y + + self.dead = False + + def draw(self): + ugfx.area(int(self.previousX-self.size/2),int(self.previousY-self.size/2),int(self.size),int(self.size),bgColor) + ugfx.area(int(self.x-self.size/2),int(self.y-self.size/2),int(self.size),int(self.size),ballColor) + + def update(self, topPaddle, bottomPaddle): + self.previousX = self.x + self.previousY = self.y + + self.x += self.vX + self.y += self.vY + + if self.x > SCREEN_WIDTH: + self.x = SCREEN_WIDTH + self.vX = -self.vX + + if self.x < 0: + self.x = 0 + self.vX = -self.vX + + if self.y > (SCREEN_HEIGHT - self.yDeathOffset): + if (self.x > (bottomPaddle.x - bottomPaddle.width/2)) and (self.x < (bottomPaddle.x + bottomPaddle.width/2)): + self.y = SCREEN_HEIGHT - self.yDeathOffset + self.vY = -self.vY + bottomPaddle.needsRedraw = True + else: + self.dead = True + + + if self.y < self.yDeathOffset: + if (self.x > (topPaddle.x - topPaddle.width/2)) and (self.x < (topPaddle.x + topPaddle.width/2)): + self.y = self.yDeathOffset + self.vY = -self.vY + topPaddle.needsRedraw = True + else: + self.dead = True + + def isDead(self): + return self.dead + +def one_round(): + ball = Ball() + topPaddle = Paddle(0) + bottomPaddle = Paddle(1) + + ugfx.clear(bgColor) + ugfx.backlight(100) + + ugfx.set_default_font(ugfx.FONT_TITLE) + + while True: + topPaddle.update() + bottomPaddle.update() + ball.update(topPaddle, bottomPaddle) + + if ball.isDead(): + if(ball.y > SCREEN_HEIGHT/2): + return [1,0] + else: + return [0,1] + + topPaddle.draw() + bottomPaddle.draw() + ball.draw() + + #draw the net + for i in range(0,7): + ugfx.area(int(i*2*SCREEN_WIDTH/13), int(SCREEN_HEIGHT/2-1), int(SCREEN_WIDTH/13), 3, netColor) + + ugfx.orientation(0) + ugfx.text(130, 0, "%d " % (points[0]),netColor) + ugfx.text(170, 0, "%d " % (points[1]),netColor) + ugfx.orientation(270) + + time.sleep_ms(1) + +minScore = 9 + +points = [0,0] +playing = 1 +while playing: + points[0] = 0 + points[1] = 0 + + while (points[0] < minScore) and (points[1] < minScore): + score = one_round() + + points[0] = points[0] + score[0] + points[1] = points[1] + score[1] + + ugfx.area(0,0,ugfx.width(),ugfx.height(),0) + + ugfx.orientation(90) + ugfx.set_default_font(ugfx.FONT_TITLE) + ugfx.text(30, 138, "GAME ",ballColor) + ugfx.text(30, 158, "OVER ",ballColor) + + ugfx.set_default_font(ugfx.FONT_SMALL) + ugfx.text(70, 220, "Score: %d - %d " % (points[0], points[1]), ballColor) + ugfx.text(36, 260, "Press A to play again ", ballColor) + ugfx.text(40, 280, "Press MENU to quit " , ballColor) + + ugfx.orientation(270) + ugfx.set_default_font(ugfx.FONT_TITLE) + ugfx.text(30, 138, "GAME ",ballColor) + ugfx.text(30, 158, "OVER ",ballColor) + + ugfx.set_default_font(ugfx.FONT_SMALL) + ugfx.text(70, 220, "Score: %d - %d " % (points[1], points[0]), ballColor) + ugfx.text(36, 260, "Press A to play again ", ballColor) + ugfx.text(40, 280, "Press MENU to quit ", ballColor) + + while True: + sleep.wfi() + if buttons.is_triggered(Buttons.BTN_A): + break + + if buttons.is_triggered(Buttons.BTN_Menu): + playing = 0 + break + +app.restart_to_default() + diff --git a/praise_horse_worship_melon/horse.gif b/praise_horse_worship_melon/horse.gif new file mode 100644 index 0000000..217d976 Binary files /dev/null and b/praise_horse_worship_melon/horse.gif differ diff --git a/praise_horse_worship_melon/loading.gif b/praise_horse_worship_melon/loading.gif new file mode 100644 index 0000000..9eedf5b Binary files /dev/null and b/praise_horse_worship_melon/loading.gif differ diff --git a/praise_horse_worship_melon/main.py b/praise_horse_worship_melon/main.py new file mode 100644 index 0000000..c3b1b20 --- /dev/null +++ b/praise_horse_worship_melon/main.py @@ -0,0 +1,58 @@ +"""Praise the Horse or Worship the Melon, directly from your badge +""" +___name___ = "Praise Horse! Worship Melon!" +___license___ = "MIT" +___dependencies___ = ["app","wifi", "buttons", "ugfx_helper"] +___categories___ = ["Other"] + +import ugfx, wifi, utime, ugfx_helper, buttons +from tilda import Buttons +import random +from app import App + + +def loading_screen(): + logo = 'praise_horse_worship_melon/loading.gif' + ugfx.area(0,0,ugfx.width(),ugfx.height(),0xFFFF) + ugfx.display_image(2,2,logo) + ugfx.set_default_font(ugfx.FONT_SMALL) + ugfx.text(60, 145, "Praise Horse (A)", ugfx.GREY) + ugfx.text(55, 305, "Worship Melon (B)", ugfx.GREY) + +def show_screen(type=None): + if type == "horse": + img = "praise_horse_worship_melon/horse.gif" + color = ugfx.RED + text = "HORSE!" + elif type == "melon": + img = "praise_horse_worship_melon/melon.gif" + color = ugfx.BLUE + text = "MELON!" + else: + return + + ugfx.area(0,0,ugfx.width(),ugfx.height(), color) + ugfx.display_image(0, 0,img) + ugfx.set_default_font(ugfx.FONT_MEDIUM_BOLD) + for y_offset in range(8): + ugfx.Label(0, 42 * y_offset, ugfx.width(), 20, text, parent=None, style=None, justification=ugfx.Label.CENTER) + utime.sleep_ms(100) + + utime.sleep_ms(1000) + loading_screen() + +def start(): + ugfx_helper.init() + loading_screen() + utime.sleep_ms(2000) + return True + +running = start() +while running: + if buttons.is_triggered(Buttons.BTN_A): + show_screen(type='horse') + if buttons.is_triggered(Buttons.BTN_B): + show_screen(type='melon') + if buttons.is_triggered(Buttons.BTN_Menu): + App("launcher").boot() + utime.sleep_ms(30) diff --git a/praise_horse_worship_melon/melon.gif b/praise_horse_worship_melon/melon.gif new file mode 100644 index 0000000..6996646 Binary files /dev/null and b/praise_horse_worship_melon/melon.gif differ diff --git a/pride/main.py b/pride/main.py index 240fa44..977adf5 100644 --- a/pride/main.py +++ b/pride/main.py @@ -4,15 +4,17 @@ Similar to the default homescreen, but the background is the pride flag. """ -___name___ = "Pride" +___title___ = "Pride" ___license___ = "MIT" ___categories___ = ["Homescreens"] -___dependencies___ = ["homescreen", "app"] +___dependencies___ = ["homescreen", "app", "buttons"] from app import restart_to_default import ugfx import homescreen +from tilda import Buttons +import buttons homescreen.init() @@ -25,56 +27,94 @@ info_height = 20 # Maximum length of name before downscaling max_name = 8 -# Orientation for other people to see -ugfx.orientation(90) +flags = { + 'LGBT': [0xE70000, 0xFF8C00, 0xFFEF00, 0x00811F, 0x0044FF, 0x760089], + 'Non-Binary': [0xFFF433, 0xFFFFFF, 0x9B59D0, 0x000000], + 'Trans': [0x5BCEFA, 0xF5A9B8, 0xFFFFFF, 0xF5A9B8, 0x5BCEFA], + 'Asexual': [0x000000, 0xA3A3A3, 0xFFFFFF, 0x800080], + 'Bisexual': [0xFF0080, 0xFF0080, 0xA349A4, 0x0000FF, 0x0000FF], + 'Pansexual': [0xFF218E, 0xFCD800, 0x0194FC] +} -# Pride flag colours -colours = [0xE70000, 0xFF8C00, 0xFFEF00, 0x00811F, 0x0044FF, 0x760089] -# Draw each "band" of colour in the flag -colour_width = ugfx.width() / len(colours) -for num, colour in enumerate(colours): - width_loc = int(num * colour_width) - ugfx.area(width_loc, 0, int(colour_width), 320, ugfx.html_color(colour)) +def draw_flag(colours): + # Orientation for other people to see + ugfx.orientation(90) -# Message to display -prefix_message = "Hi I'm" + # Draw each "band" of colour in the flag + colour_width = ugfx.width() / len(colours) + for num, colour in enumerate(colours): + width_loc = int(num * colour_width) + flag_height = ugfx.height() - (name_height + info_height) + ugfx.area(width_loc, info_height, int(colour_width), flag_height, ugfx.html_color(colour)) -ugfx.set_default_font(ugfx.FONT_NAME) -# Calc center of screen -center = (int(ugfx.width() / 2), int(ugfx.height() / 2)) -# Can't use label since the background covers the flag -ugfx.text(50, center[1] + name_height, prefix_message, ugfx.WHITE) +def draw_name(): + # Orientation for other people to see + ugfx.orientation(90) -# Process name -given_name = homescreen.name("Set your name in the settings app") -if len(given_name) <= max_name: ugfx.set_default_font(ugfx.FONT_NAME) -else: - ugfx.set_default_font(ugfx.FONT_MEDIUM_BOLD) -# Draw name -ugfx.Label(0, ugfx.height() - name_height, ugfx.width(), name_height, given_name, justification=ugfx.Label.CENTER) + + # Process name + given_name = homescreen.name("Set your name in the settings app") + if len(given_name) <= max_name: + ugfx.set_default_font(ugfx.FONT_NAME) + else: + ugfx.set_default_font(ugfx.FONT_MEDIUM_BOLD) + # Draw name + ugfx.Label(0, ugfx.height() - name_height, ugfx.width(), name_height, given_name, justification=ugfx.Label.CENTER) -# Draw for the user to see -ugfx.orientation(270) -ugfx.set_default_font(ugfx.FONT_SMALL) +def draw_user_info(): + # Draw for the user to see + ugfx.orientation(270) + # Calc width center of screen + center_width = int(ugfx.width() / 2) + ugfx.set_default_font(ugfx.FONT_SMALL) -# WiFi/Battery update loop -while True: ugfx.area(0, ugfx.height() - info_height, ugfx.width(), info_height, ugfx.WHITE) wifi_strength_value = homescreen.wifi_strength() if wifi_strength_value: wifi_message = 'WiFi: %s%%' % int(wifi_strength_value) - wifi_text = ugfx.text(center[0], ugfx.height() - info_height, wifi_message, ugfx.BLACK) + ugfx.text(center_width, ugfx.height() - info_height, wifi_message, ugfx.BLACK) battery_value = homescreen.battery() if battery_value: battery_message = 'Battery: %s%%' % int(battery_value) - battery_text = ugfx.text(0, ugfx.height() - info_height, battery_message, ugfx.BLACK) + ugfx.text(0, ugfx.height() - info_height, battery_message, ugfx.BLACK) + +# Set variables for WiFi/Battery loop +selection_change = True +flag_names = list(flags.keys()) +selection = flag_names.index('LGBT') + +# WiFi/Battery update loop +draw_name() +while True: + # Buttons will cycle when it reaches either side of the list + if buttons.is_pressed(Buttons.JOY_Left): + if selection > 0: + selection -= 1 + else: + selection = len(flags) - 1 + selection_change = True + + elif buttons.is_pressed(Buttons.JOY_Right): + if selection < len(flags) - 1: + selection += 1 + else: + selection = 0 + selection_change = True + + # Only triggers if the selection has changed + if selection_change: + draw_flag(flags[flag_names[selection]]) + selection_change = False + + # Redraw time-sensitive info on each iteration + draw_user_info() homescreen.sleep_or_exit(1.5) restart_to_default() diff --git a/pybcdc.inf b/pybcdc.inf new file mode 100644 index 0000000..2b8c09a --- /dev/null +++ b/pybcdc.inf @@ -0,0 +1,92 @@ +; Windows USB CDC ACM Setup File +; Based on INF files which were: +; Copyright (c) 2000 Microsoft Corporation +; Copyright (C) 2007 Microchip Technology Inc. +; Likely to be covered by the MLPL as found at: +; . + +[Version] +Signature="$Windows NT$" +Class=Ports +ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318} +Provider=%MFGNAME% +LayoutFile=layout.inf +DriverVer=03/11/2010,5.1.2600.3 + +[Manufacturer] +%MFGNAME%=DeviceList, NTamd64 + +[DestinationDirs] +DefaultDestDir=12 + +;--------------------------------------------------------------------- +; Windows 2000/XP/Server2003/Vista/Server2008/7 - 32bit Sections + +[DriverInstall.nt] +include=mdmcpq.inf +CopyFiles=DriverCopyFiles.nt +AddReg=DriverInstall.nt.AddReg + +[DriverCopyFiles.nt] +usbser.sys,,,0x20 + +[DriverInstall.nt.AddReg] +HKR,,DevLoader,,*ntkern +HKR,,NTMPDriver,,usbser.sys +HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider" + +[DriverInstall.nt.Services] +AddService=usbser, 0x00000002, DriverService.nt + +[DriverService.nt] +DisplayName=%SERVICE% +ServiceType=1 +StartType=3 +ErrorControl=1 +ServiceBinary=%12%\usbser.sys + +;--------------------------------------------------------------------- +; Windows XP/Server2003/Vista/Server2008/7 - 64bit Sections + +[DriverInstall.NTamd64] +include=mdmcpq.inf +CopyFiles=DriverCopyFiles.NTamd64 +AddReg=DriverInstall.NTamd64.AddReg + +[DriverCopyFiles.NTamd64] +usbser.sys,,,0x20 + +[DriverInstall.NTamd64.AddReg] +HKR,,DevLoader,,*ntkern +HKR,,NTMPDriver,,usbser.sys +HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider" + +[DriverInstall.NTamd64.Services] +AddService=usbser, 0x00000002, DriverService.NTamd64 + +[DriverService.NTamd64] +DisplayName=%SERVICE% +ServiceType=1 +StartType=3 +ErrorControl=1 +ServiceBinary=%12%\usbser.sys + +;--------------------------------------------------------------------- +; Vendor and Product ID Definitions + +[SourceDisksFiles] +[SourceDisksNames] +[DeviceList] +%DESCRIPTION%=DriverInstall, USB\VID_f055&PID_9800&MI_00, USB\VID_f055&PID_9800&MI_01 + +[DeviceList.NTamd64] +%DESCRIPTION%=DriverInstall, USB\VID_f055&PID_9800&MI_00, USB\VID_f055&PID_9800&MI_01 + +;--------------------------------------------------------------------- +; String Definitions + +[Strings] +MFGFILENAME="pybcdc" +MFGNAME="Micro Python" +DESCRIPTION="Pyboard USB Comm Port" +SERVICE="USB Serial Driver" diff --git a/review_helper/main.py b/review_helper/main.py index 8a799dc..db21e2a 100644 --- a/review_helper/main.py +++ b/review_helper/main.py @@ -1,6 +1,6 @@ """Helps to test incoming PRs""" -___name___ = "PR Review Helper" +___title___ = "PR Review Helper" ___license___ = "MIT" ___categories___ = ["System"] ___dependencies___ = ["dialogs", "app", "ugfx_helper", "badge_store", "http", "stack_nav", "wifi"] diff --git a/screendisco/main.py b/screendisco/main.py index 0615914..cbea1c0 100644 --- a/screendisco/main.py +++ b/screendisco/main.py @@ -1,9 +1,9 @@ """ Flashes random colours on your screen "" By Pez (@Pezmc) """ -___name___ = "Screen Party" +___title___ = "Screen Party" ___license___ = "MIT" -___dependencies___ = ["ugfx_helper", "sleep", "random"] +___dependencies___ = ["ugfx_helper", "sleep"] ___categories___ = ["Homescreens"] ___bootstrapped___ = False diff --git a/sequencer/main.py b/sequencer/main.py index 987b95d..b9746d3 100644 --- a/sequencer/main.py +++ b/sequencer/main.py @@ -3,7 +3,7 @@ Annoy your friends! Annoy your enemies! Annoy yourself! Maybe (maybe) make music! """ -___name___ = "Sequencer" +___title___ = "Sequencer" ___license___ = "MIT" ___categories___ = ["Sound"] ___dependencies___ = ["speaker", "buttons", "ugfx_helper", "app", "shared/sequencer_info.png"] diff --git a/serendipity/main.py b/serendipity/main.py new file mode 100644 index 0000000..05c5f65 --- /dev/null +++ b/serendipity/main.py @@ -0,0 +1,32 @@ +"""Happy accidents or unplanned, fortunate discoveries.""" + +___name___ = "serendipity" +___license___ = "MIT" +___dependencies___ = ["sleep", "app", "ugfx_helper"] +___categories___ = ["Villages"] + +import ugfx_helper, os, wifi, ugfx, http, time, sleep, app +from tilda import Buttons + +# initialize screen +ugfx_helper.init() +ugfx.clear(ugfx.BLACK) + +img = [ugfx.Image("serendipity/sun.png"), + ugfx.Image("serendipity/world.png")] + +ugfx.backlight(100) + +n = 0 +ugfx.display_image( 0, 0, img[n] ) + +while True: + + if Buttons.is_pressed(Buttons.BTN_B): + break + elif Buttons.is_pressed(Buttons.BTN_A): + n = (n+1) % 2 + ugfx.display_image( 0, 0, img[n] ) + +ugfx.clear() +app.restart_to_default() \ No newline at end of file diff --git a/serendipity/sun.png b/serendipity/sun.png new file mode 100644 index 0000000..c0cc961 Binary files /dev/null and b/serendipity/sun.png differ diff --git a/serendipity/world.png b/serendipity/world.png new file mode 100644 index 0000000..38104dc Binary files /dev/null and b/serendipity/world.png differ diff --git a/settings/main.py b/settings/main.py index d7b80a4..f0bcdfe 100644 --- a/settings/main.py +++ b/settings/main.py @@ -11,7 +11,7 @@ Todo: """ -___name___ = "Settings" +___title___ = "Settings" ___license___ = "MIT" ___dependencies___ = ["dialogs", "ugfx_helper", "database", "app", "stack_nav", "wifi"] ___categories___ = ["System"] @@ -33,12 +33,19 @@ def settings_startup_app(state): def settings_wifi(state): wifi.choose_wifi() +def settings_launcher(state): + apps = app.get_apps("Launcher") + selection = prompt_option([{"title": a.title, "app": a} for a in apps], text="Select App:", none_text="Back", title="Set default launcher") + if selection: + app.write_launch_file(selection["app"].name, "default_launcher.txt") + def settings_main(state): return selection({ "Homescreen Name": change_database_string("Set your name", "homescreen.name"), "Homescreen Callsign": change_database_string("Set your callsign", "homescreen.callsign"), "Wifi": settings_wifi, "Startup app": settings_startup_app, + "Default Launcher": settings_launcher, "Badge Store": settings_badge_store }, none_text="Exit") diff --git a/settlers/main.py b/settlers/main.py new file mode 100644 index 0000000..5dc7372 --- /dev/null +++ b/settlers/main.py @@ -0,0 +1,187 @@ +"""Settlers of Catan game board generator""" + +___name___ = "settlers" +___license___ = "MIT" +___dependencies___ = ["ugfx_helper", "sleep"] +___categories___ = ["Games"] +___bootstrapped___ = False + +import random, ugfx, ugfx_helper, math, time, buttons +from app import App, restart_to_default +from tilda import Buttons + +ugfx_helper.init() +ugfx.clear(ugfx.BLACK) + +""" +This was an experiment in drawing hexagons. Some notes: + +Screen coords are x,y values that locate pixels on the physical display: + +0,0 → → 240,0 + ↓ ↓ +0,320 → 240,320 + +Hex coords are x,y,z values that locate the relative positions of hexagons: + + 0,1,-1 +-1,1,0 ↖ ↑ ↗ 1,0,-1 + 0,0,0 +-1,0,1 ↙ ↓ ↘ 1,-1,0 + 0,-1,1 + +Converting between the two systems can be done by multiplying the x and y +coordinates against a matrix. When converting to hex coords, the z value +can be computed from the new x and y values because x + y + z must always +equal zero. + +""" + +class Hex: + # Constant matrix used to convert from hex coords to screen coords + matrix = [3.0 * 0.5, 0.0, math.sqrt(3.0) * 0.5, math.sqrt(3.0)] + + # The screen coordinate to use as the origin for hex coordinates, + # the centre of hex 0,0,0 will be at this coordinate + origin = [math.ceil(ugfx.width() / 2), math.ceil(ugfx.height() / 2)] + + # Size in pixels of the hex, from the centre point to each corner + size = 22 + + # Possible kinds of resource and the colour it should be rendered + kinds = { + 0: ugfx.html_color(0xd4e157), # Sheep + 1: ugfx.html_color(0xffc107), # Wheat + 2: ugfx.html_color(0x993300), # Wood + 3: ugfx.html_color(0xff0000), # Brick + 4: ugfx.html_color(0x757575), # Ore + 5: ugfx.html_color(0xffee55), # Desert (nothing) + } + + # Transformations for how to get to the neighbouring hexes + directions = { + 0: [-1, 1, 0], # South West + 1: [0, 1, -1], # South + 2: [1, 0, -1], # South East + 3: [1, -1, 0], # North East + 4: [0, -1, 1], # North + 5: [-1, 0, 1], # North West + } + + def __init__(self, coords, kind, number, robber): + """Create a new hex at the given hex coordinates, of the given kind of resource""" + # Validate coords + assert len(coords) == 3, 'Invalid number of hexagon coordinates' + assert coords[0] + coords[1] + coords[2] == 0, 'Invalid hexagon coordinate values' + self.coords = coords + + # The kind of resource hosted by this hex + self.kind = kind + + # The dice roll required to win this resource + self.number = number + + # Whether this hex contains the robber + self.robber = robber + + # Compute the screen coordinates of the centre of the hex + self.centre = Hex.to_screen_coords(self.coords[0], self.coords[1]) + + # Generate screen coordinates for each of the corners of the hex + self.corners = [] + for i in range(0, 6): + angle = 2.0 * math.pi * (0 - i) / 6 + offset = [Hex.size * math.cos(angle), Hex.size * math.sin(angle)] + self.corners.append([round(self.centre[0] + offset[0]), round(self.centre[1] + offset[1])]) + + @staticmethod + def to_screen_coords(x, y): + """Returns screen coordinates computed from the given hex coordinates""" + newX = (Hex.matrix[0] * x + Hex.matrix[1] * y) * Hex.size + newY = (Hex.matrix[2] * x + Hex.matrix[3] * y) * Hex.size + return [newX + Hex.origin[0], newY + Hex.origin[1]] + + @staticmethod + def get_neighbouring_hex_coords(coords, direction): + return [a + b for a, b in zip(coords, Hex.directions[direction])] + + def draw(self): + """Draw the hexagon to the screen""" + ugfx.fill_polygon(0, 0, self.corners, Hex.kinds[self.kind]) + text_offset = Hex.size * 0.5 + if self.robber: + ugfx.text(round(self.centre[0] - text_offset), round(self.centre[1] - text_offset), "Rb ", ugfx.BLACK) + else: + if self.kind != 5: + ugfx.text(round(self.centre[0] - text_offset), round(self.centre[1] - text_offset), "{} ".format(self.number), ugfx.BLACK) + + def clear(self): + ugfx.fill_polygon(0, 0, self.corners, ugfx.BLACK) + + +def board_setup(resources, numbers): + """Generate a random game board""" + + # Two rings of hexes around the centre + radius = 2 + # Choose a starting hex on the outermost ring of hexes + choice = random.randrange(0, 6) + coords = [0, 0, 0] + for i in range(radius): + coords = [a + b for a, b in zip(coords, Hex.directions[choice])] + + # Copy lists so we can edit them with impunity + r_copy = resources.copy() + n_copy = numbers.copy() + + hexes = [] + while radius > 0: + # From the starting hex, go radius hexes in each of the 6 directions + for i in list(range((choice + 2) % 6, 6)) + list(range(0, (choice + 2) % 6)): + for j in range(radius): + # The resources are picked at random from the list + resource = r_copy.pop(random.randrange(0, len(r_copy))) + # But the dice roll numbers are picked in order, unless it's + # the desert in which case that is always 7 + if resource == 5: + number = 7 + else: + number = n_copy.pop(0) + hexes.append(Hex(coords, resource, number, number == 7)) + coords = Hex.get_neighbouring_hex_coords(coords, i) + + # Go into the next ring of hexes (opposite direction of starting choice) + coords = Hex.get_neighbouring_hex_coords(coords, (choice + 3) % 6) + radius = radius - 1 + resource = r_copy.pop() + if resource == 5: + number = 7 + else: + number = n_copy.pop(0) + hexes.append(Hex(coords, resource, number, number == 7)) + return hexes + + +# List of resources (pre-randomised to combat the not-very random number +# generator) and dice rolls (these have a strict order) for 2-4 player games +resources = [4, 0, 1, 4, 4, 2, 5, 3, 2, 1, 2, 2, 1, 0, 3, 0, 3, 1, 0] +numbers = [5, 2, 6, 3, 8, 10, 9, 12, 11, 4, 8, 10, 9, 4, 5, 6, 3, 11] + +def draw(): + hexes = board_setup(resources, numbers) + for h in hexes: + h.clear() + time.sleep_ms(100) + h.draw() + +ugfx.text(5, 5, 'Press A to generate another ', ugfx.WHITE) +draw() + +# Main Loop +while True: + if buttons.is_triggered(tilda.Buttons.BTN_A): + draw() + elif buttons.is_triggered(tilda.Buttons.BTN_Menu): + break + time.sleep_ms(5) +restart_to_default() diff --git a/shared/nyan/0.png b/shared/nyan/0.png new file mode 100644 index 0000000..7a9128c Binary files /dev/null and b/shared/nyan/0.png differ diff --git a/shared/nyan/1.png b/shared/nyan/1.png new file mode 100644 index 0000000..73ce179 Binary files /dev/null and b/shared/nyan/1.png differ diff --git a/shared/nyan/2.png b/shared/nyan/2.png new file mode 100644 index 0000000..04db181 Binary files /dev/null and b/shared/nyan/2.png differ diff --git a/shared/nyan/3.png b/shared/nyan/3.png new file mode 100644 index 0000000..3589976 Binary files /dev/null and b/shared/nyan/3.png differ diff --git a/shared/nyan/4.png b/shared/nyan/4.png new file mode 100644 index 0000000..187ed61 Binary files /dev/null and b/shared/nyan/4.png differ diff --git a/shared/nyan/5.png b/shared/nyan/5.png new file mode 100644 index 0000000..6275a5a Binary files /dev/null and b/shared/nyan/5.png differ diff --git a/sms/main.py b/sms/main.py index ba61523..c1e0211 100644 --- a/sms/main.py +++ b/sms/main.py @@ -1,6 +1,6 @@ """SMS app for reading and sending messages """ -___name___ = "SMS" +___title___ = "SMS" ___license___ = "MIT" ___dependencies___ = ["app", "dialogs", "sim800", "ugfx_helper"] ___categories___ = ["System"] diff --git a/snake/main.py b/snake/main.py index 13761e8..86eb8d3 100644 --- a/snake/main.py +++ b/snake/main.py @@ -1,11 +1,11 @@ """Snake!""" -___name___ = "Snake" +___title___ = "Snake" ___license___ = "MIT" ___categories___ = ["Games"] -___dependencies___ = ["dialogs", "app", "ugfx_helper", "random", "sleep", "buttons"] +___dependencies___ = ["dialogs", "app", "ugfx_helper", "sleep", "buttons"] -import math, ugfx, ugfx_helper, random, sleep, buttons +import app, math, ugfx, ugfx_helper, random, sleep, buttons from tilda import Buttons ugfx_helper.init() diff --git a/soundsarecool/main.py b/soundsarecool/main.py new file mode 100644 index 0000000..fea225a --- /dev/null +++ b/soundsarecool/main.py @@ -0,0 +1,97 @@ +''' +A small N channel music player a la 8088 MPH +(Demo is only two channels) + +By Molive^SLP +''' + +___name___ = "Arp Music Player" +___title___ = "Arp Music Player" +___license___ = "WTFPL" +___dependencies___ = ["ugfx_helper", "speaker"] +___categories___ = ["Demo","Sound"] + +import speaker +import utime + +from app import restart_to_default +from tilda import Buttons + +channels = [ #Supports up to n simultaneous channels. + #Each channel will loop it's data independently from the others + [ #and channels do not need the same length of data in them. + ('F4',2), #No. of channels is decided on startup, and all channels are running at any one time. + ('D#4',2), + ('D4',2), + ('C#4',1), + ('D#4',1), + ], + + ##[ + ##('A#3',4), + ##], + + ##[ + ##('C4',4), + ##], + + [ + ('F5',0.125), + ('G#5',0.25), + ('F5',0.125), + ('A#5',0.25), + ('F5',0.125), + ('C6',0.25), + ('F5',0.125), + ('A#5',0.25), + ('F5',0.25), + ('G#5',0.25), + ], + + ] + +def prt(s): + ugfx.clear() + ugfx.text(5,5,str(s),0) + +import ugfx +ugfx.init() + +prt("RUNNING SOUND TEST") + +utime.sleep(1) + +prt("Use menu to reboot") + +speaker.enabled(True) + +channel_waits = [ #Add more of these to increase the max channel count + [-1,utime.ticks_ms()], + [-1,utime.ticks_ms()], + [-1,utime.ticks_ms()], + [-1,utime.ticks_ms()], + ] + +current_channel = 0 + +while True: #Main awesome loop which handles all music channels. Can handle and arbitrary amount, but lags at high numbers. + for channel in channels: + ##print(channel_waits[current_channel][1]) #Uncomment some of these for more debug info :P + if channel_waits[current_channel][1] == 0 or channel_waits[current_channel][1] <= utime.ticks_ms(): + print("CHANGING CHANNEL "+str(current_channel)) + channel_waits[current_channel][0] += 1 + if channel_waits[current_channel][0] == len(channel): + channel_waits[current_channel][0] = 0 + channel_waits[current_channel][1] += (channels[current_channel][channel_waits[current_channel][0]][1]*1000.0) + speaker.note(channel[channel_waits[current_channel][0]][0]) + ##print(channel[channel_waits[current_channel][0]]) + if Buttons.is_pressed(Buttons.BTN_Menu): + print("BAIL BAIL BAIL") + restart_to_default() + current_channel += 1 + if current_channel == len(channels): + current_channel = 0 + ##print(current_channel) + utime.sleep(0.03) #Decrease this for more accurate but weirder arps. Comment it out for insane madness + +restart_to_default() \ No newline at end of file diff --git a/speedlauncher/main.py b/speedlauncher/main.py new file mode 100644 index 0000000..a78725d --- /dev/null +++ b/speedlauncher/main.py @@ -0,0 +1,118 @@ +"""Launcher for apps currently installed""" + +___name___ = "Speed Launcher" +___license___ = "WTFPL" +___categories___ = ["System", "Launcher"] +___dependencies___ = ["app", "ugfx_helper"] +___launchable___ = False +___bootstrapped___ = False + +import ugfx_helper, ugfx, math, buttons +from app import * +from tilda import Buttons + +APPS_PER_PAGE = 12 +EMF_PURPLE = 0x800080 + +ugfx_helper.init() +ugfx.clear(ugfx.html_color(EMF_PURPLE)) + +ugfx.set_default_font(ugfx.FONT_SMALL) +style = ugfx.Style() +style.set_enabled([ugfx.WHITE, ugfx.html_color(EMF_PURPLE), ugfx.html_color(EMF_PURPLE), ugfx.html_color(EMF_PURPLE)]) +style.set_background(ugfx.html_color(EMF_PURPLE)) +ugfx.set_default_style(style) + +loadMsg = ugfx.Label(0, 90, ugfx.width(), 20, "Loading apps...", justification=ugfx.Label.CENTER) + +# Load apps in a colourList +all_apps = [{"title": a.title, "app": a} for a in get_apps()] + +# Sort apps by alphabetical order +all_apps.sort(key=lambda a: a['title']) +total_pages = math.ceil(len(all_apps) / APPS_PER_PAGE) + +ugfx.clear(ugfx.html_color(EMF_PURPLE)) + +keypad = [ + Buttons.BTN_1, + Buttons.BTN_2, + Buttons.BTN_3, + Buttons.BTN_4, + Buttons.BTN_5, + Buttons.BTN_6, + Buttons.BTN_7, + Buttons.BTN_8, + Buttons.BTN_9, + Buttons.BTN_Star, + Buttons.BTN_0, + Buttons.BTN_Hash +] + +keypadLabels = [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "*", + "0", + "#" +] + +def showPage(): + global current_page + # avoid out of bounds errors + current_page = max(1, min(current_page, total_pages)) + + start = (current_page - 1) * APPS_PER_PAGE + end = start + APPS_PER_PAGE + apps_on_current_page = all_apps[start:end] + + # Refresh page + ugfx.clear(ugfx.html_color(EMF_PURPLE)) + + # Write current page number and arrows + ugfx.Label(0, 20, ugfx.width(), 20, "Page {} of {}".format(current_page, total_pages), justification=ugfx.Label.CENTER) + + if current_page > 1: + ugfx.fill_polygon(10, 16, [[0, 10], [15, 20], [15, 0]], ugfx.WHITE) + + if current_page < total_pages: + ugfx.fill_polygon(ugfx.width() - 30, 16, [[0, 0], [15, 10], [0, 20]], ugfx.WHITE) + + # Write app numbers and names + i = 0 + yOffset = 45 + xOffset = 0 + for a in apps_on_current_page: + # xOffset = (i % 3) * 8 # offset lines to match the physical layout of the keypad + ugfx.area(20 + xOffset, yOffset + 2, 20, 20, ugfx.WHITE) + ugfx.text(23 + xOffset, yOffset + 3, keypadLabels[i] + " ", EMF_PURPLE) + + ugfx.Label(46 + xOffset, yOffset + 3, ugfx.width(), 20, a['title'], justification=ugfx.Label.LEFT) + yOffset = yOffset + 22 + i = i + 1 + + while True: + for key in keypad: + keyIndex = keypad.index(key) + if buttons.is_pressed(key) and (keyIndex < len(apps_on_current_page)): + apps_on_current_page[keyIndex]['app'].boot() + break + + if buttons.is_triggered(Buttons.JOY_Right) and (current_page is not total_pages): + current_page = current_page + 1 + return + if buttons.is_triggered(Buttons.JOY_Left) and (current_page is not 1): + current_page = current_page - 1 + return + +current_page = 1 + +while True: + showPage() diff --git a/sponsors/main.py b/sponsors/main.py index 759aec8..5f8a5b1 100644 --- a/sponsors/main.py +++ b/sponsors/main.py @@ -1,6 +1,6 @@ """A big "thank you" to all our Sponsors who made this year's badge possible!""" -___name___ = "Sponsors" +___title___ = "Sponsors" ___license___ = "MIT" ___dependencies___ = ["wifi", "http", "ugfx_helper", "sleep", "app"] ___categories___ = ["EMF"] diff --git a/square_home/main.py b/square_home/main.py index 0a50b29..f948086 100644 --- a/square_home/main.py +++ b/square_home/main.py @@ -1,6 +1,6 @@ """A home screen with squares that spin""" -___name___ = "Squares home" +___title___ = "Squares home" ___license___ = "MIT" ___dependencies___ = ["sleep", "app", "ugfx_helper", "buttons", "homescreen"] ___categories___ = ["Homescreens"] diff --git a/star_wars/main.py b/star_wars/main.py index a5a4ade..55209c8 100644 --- a/star_wars/main.py +++ b/star_wars/main.py @@ -3,7 +3,7 @@ Will play music, maybe """ -___name___ = "Play Music" +___title___ = "Star Wars Music" ___license___ = "MIT" ___categories___ = ["Sound"] ___dependencies___ = ["speaker", "shared/sw.png", "buttons"] diff --git a/stories/main.py b/stories/main.py index d019bd6..84445bf 100755 --- a/stories/main.py +++ b/stories/main.py @@ -1,6 +1,6 @@ """Read stories from twentythreemillionstories.org""" -___name___ = "twenty-three million stories" +___title___ = "twenty-three million stories" ___license___ = "MIT" ___categories___ = ["Other"] ___dependencies___ = [ "app", "dialogs", "http", "ugfx_helper", "sleep" ] diff --git a/synth/main.py b/synth/main.py index d73a822..8dfae8c 100644 --- a/synth/main.py +++ b/synth/main.py @@ -3,7 +3,7 @@ Todo: fix this, it doesn't work at at the moment """ -___name___ = "Synthesizers" +___title___ = "Synthesizers" ___license___ = "MIT" ___categories___ = ["Sound"] ___dependencies___ = ["speaker", "buttons", "ugfx_helper", "app"] diff --git a/sysinfo/main.py b/sysinfo/main.py index 54e53c4..1c32baf 100644 --- a/sysinfo/main.py +++ b/sysinfo/main.py @@ -1,6 +1,6 @@ """This app tests all the onboard sensors and system info""" -___name___ = "System Info" +___title___ = "System Info" ___license___ = "MIT" ___dependencies___ = ["sleep", "app", "sim800"] ___categories___ = ["EMF", "System"] @@ -9,6 +9,7 @@ ___bootstrapped___ = True #import ugfx, os, time, sleep, app, sim800 import ugfx, app, sim800 +import os from tilda import Buttons from tilda import Sensors from machine import ADC @@ -38,6 +39,8 @@ else: ugfx.Label(5, 185, 240, 15, simversion) +ugfx.Label(5, 215, 240, 30, "Badge firmware version:\n{}".format(os.uname().version)) + ugfx.Label(5, 300, 240, 15, "** Hold A or B or MENU to exit **") diff --git a/tilda_tools.bat b/tilda_tools.bat new file mode 100644 index 0000000..76c731f --- /dev/null +++ b/tilda_tools.bat @@ -0,0 +1,2 @@ +@echo off +python "%CD%/.development\tilda_tools.py" %* \ No newline at end of file diff --git a/tildatorch/main.py b/tildatorch/main.py index 73bbcd2..5837bdd 100644 --- a/tildatorch/main.py +++ b/tildatorch/main.py @@ -1,6 +1,6 @@ """This app goes with the Torch Tutorial""" -___name___ = "Tilda Torch" +___title___ = "Tilda Torch" ___license___ = "MIT" ___dependencies___ = ["sleep", "app"] ___categories___ = ["EMF"] diff --git a/tildr/main.py b/tildr/main.py index 521095b..5006d31 100644 --- a/tildr/main.py +++ b/tildr/main.py @@ -1,6 +1,6 @@ """ Tildr Dating """ -___name___ = "Tildr Dating" +___title___ = "Tildr Dating" ___license___ = "MIT" ___dependencies___ = ["wifi", "http", "ugfx_helper", "sleep", "dialogs", "sim800", "database"] ___categories___ = ["Other"] diff --git a/tildr/profile.py b/tildr/profile.py index f4d0fac..5692d0a 100644 --- a/tildr/profile.py +++ b/tildr/profile.py @@ -4,7 +4,7 @@ import database, ujson, sim800, dialogs, http def get_profile(): profile_json = database.get("tildr_profile") if profile_json is None: - return {} + return None profile = ujson.loads(profile_json) return profile diff --git a/tinda/main.py b/tinda/main.py index 7095b8c..b1bac7e 100644 --- a/tinda/main.py +++ b/tinda/main.py @@ -1,7 +1,7 @@ """ TiNDA: A dating app for TiLDA. Find your perfect EMF match! """ -___name___ = "tinda" +___title___ = "TiNDA" ___license___ = "WTFPL" ___dependencies___ = ["app", "buttons", "database", "dialogs", "http", "sleep", "ugfx_helper"] ___categories___ = ["Other", "EMF"] diff --git a/trains/api.py b/trains/api.py new file mode 100644 index 0000000..4da4eaf --- /dev/null +++ b/trains/api.py @@ -0,0 +1,23 @@ +import http +import ujson +from tilda import LED + +API_URL = "https://huxley.apphb.com/all/{}?expand=true&accessToken=D102521A-06C6-44C9-8693-7A0394C757EF" + +def get_trains(station_code='LBG'): + print('trains/api: Getting trains for {}'.format(station_code)) + station_data = None + + LED(LED.RED).on() # Red for total get_trains + try: + station_json = http.get(API_URL.format( + station_code)).raise_for_status().content + LED(LED.GREEN).on() # Green for parsing + station_data = ujson.loads(station_json) + except Exception as e: + print('Error:') + print(e) + + LED(LED.RED).off() + LED(LED.GREEN).off() + return station_data diff --git a/trains/bottom.gif b/trains/bottom.gif new file mode 100644 index 0000000..3966ce8 Binary files /dev/null and b/trains/bottom.gif differ diff --git a/trains/departure_screen.py b/trains/departure_screen.py new file mode 100644 index 0000000..3657975 --- /dev/null +++ b/trains/departure_screen.py @@ -0,0 +1,117 @@ +import sleep +import ugfx +import database +from time import time +from homescreen import time_as_string +from tilda import Buttons +from trains.screen import Screen, S_CONTINUE, S_TO_SETTINGS, S_EXIT +from trains.api import get_trains +from trains.utils import get_departure, get_title, is_red + +UPDATE_INTERVAL_SECS = 30 + +class DepartureScreen(Screen): + def __init__(self): + self.station_data = None + self.has_error = False + self.last_update = 0 + self.should_redraw = True + + self._names = None + self._old_names = None + + def enter(self): + self.next_state = S_CONTINUE + self.station_code = database.get('trains.station_code', 'LBG') + self.last_update = 0 + Buttons.enable_interrupt( + Buttons.BTN_A, + lambda t: self.set_next_state(S_TO_SETTINGS), + on_press=True, + on_release=False + ) + Buttons.enable_interrupt( + Buttons.BTN_Menu, + lambda t: self.set_next_state(S_EXIT), + on_press=True, + on_release=False + ) + + def set_next_state(self, s): + self.next_state = s + + def update(self): + now = time() + if self.last_update < (now - UPDATE_INTERVAL_SECS): + print('trains/departure_screen: Updating data') + new_station_data = get_trains(self.station_code) + if new_station_data == None: + self.has_error = True + self.should_redraw = True + else: + self.station_data = new_station_data + self.has_error = False + self.should_redraw = True + self.last_update = now + + def tick(self): + self.update() + + if self.should_redraw: + if self.station_data == None: + self.show_error() + else: + self.show_trains() + else: + self._destroy_old_names() + + sleep.sleep_ms(500) + + return self.next_state + + def _get_names_container(self): + if self._names != None: + self._names.hide() + self._old_names = self._names + names = ugfx.Container(0, 25, 190, 295) + self._names = names + return names + + def _destroy_old_names(self): + if self._old_names != None: + self._old_names.destroy() + self._old_names = None + def _destroy_names(self): + if self._names != None: + self._names.destroy() + self._names = None + + def show_trains(self): + ugfx.clear() + ugfx.area(0, 0, 240, 25, ugfx.RED if self.has_error else ugfx.GRAY) + title = get_title(self.station_data['locationName'], self.has_error) + ugfx.text(5, 5, title, ugfx.WHITE if self.has_error else ugfx.BLACK) + ugfx.text(195, 5, time_as_string(), ugfx.BLUE) + + names = self._get_names_container() + names.show() + row_num = 0 + for service in self.station_data['trainServices']: + departure = get_departure(service) + if departure: + names.text(5, 15 * row_num, service['destination'][0]['locationName'], ugfx.BLACK) + ugfx.text(195, 25 + (15 * row_num), departure,ugfx.RED if is_red(service) else ugfx.BLUE) + row_num += 1 + + ugfx.display_image(0, 300, 'trains/bottom.gif') + self.should_redraw = False + + def show_error(self): + ugfx.clear() + ugfx.text(5, 5, 'Error :(', ugfx.RED) + self.should_redraw = False + + def exit(self): + self._destroy_old_names() + self._destroy_names() + Buttons.disable_all_interrupt() diff --git a/trains/main.py b/trains/main.py new file mode 100644 index 0000000..422c45a --- /dev/null +++ b/trains/main.py @@ -0,0 +1,92 @@ +"""Mini train departure board for your badge + +Configurable with which station you want to monitor +""" +___title___ = "trains" +___license___ = "MIT" +___dependencies___ = ["app", "sleep", "wifi", "http", "ugfx_helper"] +___categories___ = ["Homescreens", "Other"] +___bootstrapped___ = False + + +import database +import wifi +import ugfx +import app +import sleep +import ntp +from tilda import Buttons, LED +from trains import api +from trains import screen +from trains.departure_screen import DepartureScreen +from trains.settings_screen import SettingsScreen + +def init_screen(orientation): + # initialize screen + ugfx.clear() + ugfx.orientation(orientation) + ugfx.backlight(50) + # show initial screen + # photo credit: https://www.flickr.com/photos/remedy451/8061918891 + ugfx.display_image(0, 0, 'trains/splash.gif', 90) + + +def init(): + print('trains/main: Init') + ugfx.init() + ntp.set_NTP_time() + # ensure wifi connection + if not wifi.is_connected(): + wifi.connect(show_wait_message=True) + + +def exit(): + print('trains/main: Exit') + ugfx.clear() + app.restart_to_default() + + +app_screens = { + screen.SETTINGS: SettingsScreen(), + screen.DEPARTURES: DepartureScreen() +} + + +def get_initial_screen(): + station_code = database.get('trains.station_code', None) + if station_code == None: + return app_screens[screen.SETTINGS] + return app_screens[screen.DEPARTURES] + + +def run_screen(instance): + print('trains/main: Starting screen {}'.format(instance)) + instance.enter() + + is_running = True + next_screen_name = None + while is_running: + status, value = instance.tick() + + if status == screen.SWITCH_SCREEN: + is_running = False + next_screen_name = value + elif status == screen.EXIT_APP: + is_running = False + + print('trains/main: Stopping screen {} (next = {})'.format(instance, next_screen_name)) + instance.exit() + return next_screen_name + +init() +current_screen = get_initial_screen() +is_app_running = True +while is_app_running: + init_screen(current_screen.orientation()) + next_screen_name = run_screen(current_screen) + + if next_screen_name != None: + current_screen = app_screens[next_screen_name] + else: + is_app_running = False + exit() diff --git a/trains/screen.py b/trains/screen.py new file mode 100644 index 0000000..54752c5 --- /dev/null +++ b/trains/screen.py @@ -0,0 +1,28 @@ +CONTINUE = 1 +SWITCH_SCREEN = 2 +EXIT_APP = 3 + +DEPARTURES = 10 +SETTINGS = 11 + +S_CONTINUE = (CONTINUE, None) +S_TO_SETTINGS = (SWITCH_SCREEN, SETTINGS) +S_TO_TRAINS = (SWITCH_SCREEN, DEPARTURES) +S_EXIT = (EXIT_APP, None) + + +class Screen(): + def __init__(self): + pass + + def orientation(self): + return 90 + + def enter(self): + pass + + def tick(self): + return S_CONTINUE + + def exit(self): + pass diff --git a/trains/settings_screen.py b/trains/settings_screen.py new file mode 100644 index 0000000..42f6421 --- /dev/null +++ b/trains/settings_screen.py @@ -0,0 +1,19 @@ +import database +import ugfx +from dialogs import prompt_text +from trains.screen import Screen, S_CONTINUE, S_TO_TRAINS + +class SettingsScreen(Screen): + def __init__(self): + self.next_state = S_TO_TRAINS + + def orientation(self): + return 270 + + def tick(self): + with database.Database() as db: + crs = prompt_text('Enter your station\'s CRS code', db.get('trains.station_code', '')) + db.set('trains.station_code', crs) + + return self.next_state + \ No newline at end of file diff --git a/trains/splash.gif b/trains/splash.gif new file mode 100644 index 0000000..d3d64b0 Binary files /dev/null and b/trains/splash.gif differ diff --git a/trains/utils.py b/trains/utils.py new file mode 100644 index 0000000..fa6c902 --- /dev/null +++ b/trains/utils.py @@ -0,0 +1,19 @@ +def is_red(service): + return service['isCancelled'] or service['etd'] != 'On time' + + +def get_departure(service): + if service['isCancelled']: + return 'CANX' + + if service['etd'] == 'On time': + return service['std'] + + return service['etd'] + + +def get_title(name, has_error): + if has_error: + return 'ERR ' + name + + return name diff --git a/warm_and_wet/main.py b/warm_and_wet/main.py index 40fa4d3..6665c35 100644 --- a/warm_and_wet/main.py +++ b/warm_and_wet/main.py @@ -1,6 +1,6 @@ """ """ -___name___ = "my_app" +___title___ = "Warm and Wet" ___license___ = "MIT" ___dependencies___ = ["dialogs", "ugfx_helper", "app", "sleep"] ___categories___ = ["Other"]