How Much Computer?

I learned to program on a TRS-80 Model I. And for almost any normal need, you could get by just fine on it. You could program in BASIC, Pascal, or Z80 assembly, do word-processing, play amazing videogames and text adventures, or write your own.

There's still people using their TRS-80 as a hobby, TRS-80 Trash Talk podcast, TRS8BIT newsletter, making hardware like the MISE Model I System Expander. With the latter, it's possible to use it for some modern computing problems. I listen to the podcast out of nostalgia, but every time the urge to buy a Model I and MISE comes over me, I play with a TRS-80 emulator and remember why I shouldn't be doing that.

If that's too retro, you can get a complete Raspberry Pi setup for $100 or so, perfectly fine for some light coding (hope you like Vim, or maybe Emacs, because you're not going to run Atom on it), and most end-user tasks like word processing and email are fine. Web browsing is going to be a little challenging, you can't keep dozens or hundreds of tabs open (as I insanely do), and sites with creative modern JavaScript are going to eat it alive. It can play Minecraft, sorta; it's the crippled Pocket Edition with some Python APIs, but it's something.

I'm planning to pick one up, case-mod it inside a keyboard, and make myself a retro '80s cyberdeck, more as an art project than a practical system, but I'll make things work on it, and I want to ship something on Raspbian.

"It was hot, the night we burned Chrome. Out in the malls and plazas, moths were batting themselves to death against the neon, but in Bobby's loft the only light came from a monitor screen and the green and red LEDs on the face of the matrix simulator. I knew every chip in Bobby's simulator by heart; it looked like your workaday Ono-Sendai VII, the "Cyberspace Seven", but I'd rebuilt it so many times that you'd have had a hard time finding a square millimeter of factory circuitry in all that silicon."
—William Gibson, "Burning Chrome" (1985)

Back in the day, I would work on Pascal, C, or Scheme code in a plain text editor (ed, vi (Bill Joy's version), or steVIe) all morning, start a compile, go to lunch, come back and read the error log, go through and fix everything, recompile and go do something else, repeat until I got a good build for the day. Certainly this encouraged better code hygiene and thinking through problems instead of just hitting build, but it wasn't fun or rapid development. So that's a problem with these retro systems; the tools I use take all RAM and CPU and want more.

"Are you stealing those LCDs?" "Yeah, but I'm doing it while my code compiles."

These days, I mostly code in Atom, which is the most wasteful editor ever made but great when it's working. I expect my compiles to take seconds or less (and I don't even use the ironically-named Swift). When I do any audio editing (in theory, I might do some 3D in Unity, or video editing, but in practice I barely touch those), I can't sit there trying an effect and waiting minutes for it to burn the CPU. And I'm still and forever hooked on Elder Scrolls Online, which runs OK but not highest-FPS on my now-3-year-old iMac 5k.

For mobile text editing and a little browsing or video watching, I can use a cheap iPad, which happily gets me out of burning a pile of money on laptops. But I'm still stuck on the desktop work machine, I budget $2000 or more every 4 years for a dev and gaming Mac. Given the baseline of $8000 for an iMac Pro I'd consider useful, and whatever more the Mac Pro is going to cost, I'd better get some money put together for that.

I can already hear the cheapest-possible-computer whine of "PC Master Race" whom I consider to be literal trailer trash Nazis in need of a beating, and I'd sooner gnaw off a leg than run Windows; and Lindorks with dumpster-dived garbage computers may be fine for a little hobby coding, but useless for games, the productivity software's terrible (Gimp and OpenOffice, ugh), and the audio and graphics support are shit. The RasPi is no worse than any "real" computer running Linux.

'80s low-end computers barely more than game consoles were $200, and "high-end" with almost the same specs other than floppy disks and maybe an 80-column display were $2500 ($7921 in 2017 money!!!), but you simply couldn't do professional work on the low-end machines. Now there's a vast gulf of capability between the low-end and high-end, the price difference is the same, and I still need an expensive machine for professional work. Is that progress?

A Little Scheme

I go through phases of playing with Scheme for utility code, maybe even portable dev; while FreePascal is a better language for this, I'm frustrated by the lack of library support and the useless iOS situation.

Scheme's always been an emergency backup language; it was fun to learn back in the '80s and early '90s, and both SICP and TSPL are good books, but nobody wanted to pay for Scheme dev, and anyway the language is very annoying to write. I often treat it as a logic puzzle to get anything done, not a useful tool. But it does have good library support, and it can compile to very fast binaries, despite having GC pauses and consuming 2x as much memory as a C program. Maybe I can get better at solving problems in it, build up some libraries, and make it useful?

So the current landscape is:

  • Chez Scheme:
    • Pros:
      • Very fast to compile and at runtime, competitive with C compilers.
      • Great interactive REPL, not just a half-broken readline history like pretty much every other Scheme.
      • Debugger is reasonably good, and integrated in the REPL. All I really use myself is (trace FOO) and (inspect BAR), but non-caveman coders will make better use of it.
      • Current R6RS implementation plus extensive chezscheme library.
      • By R. Kent Dybvig, author of TSPL, and Cisco currently employs him to maintain Chez Scheme.
      • REPL environment is called a café, which I find charming. Yes, I also liked all the coffee puns and iconography from early Java programming.
    • Cons:
      • Not as widely supported by tools & documentation as Racket.
  • Racket:
    • Pros:
      • Very nice GUI.
      • Current R6RS implementation plus extensive racket library.
      • Built around making multiple languages; I don't really care about this. I loathe "Typed Racket", one of the worst combinations of ideas in history.
      • Tons of documentation.
    • Cons:
      • Mediocre performance. There's a project to rehost Racket on Chez Scheme, which would fix this, but then why use Racket?
      • Doing anything in the GUI destroys your environment, all the objects you've made, unlike any LISP or Scheme ever. So it's utterly fucking useless as an interactive REPL. I can't say enough bad things about this. ★☆☆☆☆ Kill On Sight.
  • Chicken:
    • Pros:
      • Compiles to C and thence to native binaries, with nice FFI to C libraries.
    • Cons:
      • Mostly old R5RS, with a few extension libraries.
      • Terrible REPL, only really usable as a compiled language.
  • Scheme R7RS benchmarks

Chez Scheme is the clear winner for me; if I was a novice, I might choose Racket and not realize that the REPL is a broken abomination for a while. If I was only doing C interop, Chicken would be better.

Editing in BBEdit works OK, but it doesn't know how to find function definitions. I guess Vim has current syntax, but I'm kinda over that habit unless I have to sysadmin. I have never been emacsulated and never will.

Atom's symbols list doesn't do any better. But if you do want to use it, install package language-racket (all other language-schemes are R5RS at best), and then add some file types to config.cson:

"*":
  core:
    customFileTypes:
      "source.racket": [
        "scm"
        "ss"
      ]

In any editor, any language, I use hard tabs (1 char = 1 logical indentation level, obviously), and normally tabstop at 8 chars which discourages very long nesting and encourages me to extract functions. Scheme is indentation hell, so set the tabstop to 4 spaces. (The code blocks below won't show that.)

Do not criticize my C-like paren/brace placement; I prefer clear readability of code structure to some obsolete Emacs dogma.

So, let's see it work, with hello.ss:

#!/usr/bin/env scheme-script
(import (chezscheme))
(format #t "Cheers 🍻 , ~a!~%" (car (command-line-arguments)))
(exit)
% chmod 755 hello.ss
% ./hello.ss Mark
Cheers 🍻 , Mark!

Now for something more serious:

stdlib.ss:

;; stdlib.ss
;; Copyright © 2015,2018 by Mark Damon Hughes. All Rights Reserved.
(library (stdlib)
    (export inc! dec! currentTimeMillis randomize input atoi)
    (import (chezscheme))

;; Variables

(define-syntax inc!
    (syntax-rules ()
        ((_ x)      (begin (set! x (+ x 1)) x))
        ((_ x n)    (begin (set! x (+ x n)) x))
    )
)

(define-syntax dec!
    (syntax-rules ()
        ((_ x)      (inc! x -1))
        ((_ x n)    (inc! x (- n)))
    )
)

;; Date-Time

(define (currentTimeMillis)
    (let [(now (current-time))]
        (+ (* (time-second now) 1000)
            (div0 (time-nanosecond now) 1000000))
    )
)

;; Random Numbers
;; "Anyone who attempts to generate random numbers by deterministic means is, of course, living in a state of sin." —John von Neumann

(define (randomize)
    (random-seed (bitwise-and (currentTimeMillis) #xffffffff) )
)

;; Input/Output

;; Reads a line from stdin, ends program on EOF
(define (input)
    (let [(s (get-line (current-input-port)) )]
        (if (eof-object? s)
            [begin (display "Bye!\n")
                (exit)
            ]
            s
        )
    )
)

;; Strings

;; Converts a string to an integer, 0 if invalid
(define (atoi s)
    (let [(n (string->number s))]
        (if (eqv? n #f)
            0
            (inexact->exact (truncate n))
        )
    )
)

)

guess.ss:

#!/usr/bin/env scheme-script
;; guess.ss
;; Copyright © 2015,2018 by Mark Damon Hughes. All Rights Reserved.

(import (chezscheme))
(import (stdlib))

(define (guess)
    (display "I'm thinking of a number from 1 to 100, try to guess it!\n")
    (let [(theNumber (+ (random 100) 1))]
        (define guesses 1)
        (do [(break #f)] (break)
            (format #t "Guess #~a? " guesses)
            (let [(g (atoi (input)))]
                (cond
                    [(or (<= g 0) (>= g 100))
                        (display "Try a number from 1 to 100.\n")
                    ]
                    [(< g theNumber)
                        (display "Too low!\n")
                        (inc! guesses)
                    ]
                    [(> g theNumber)
                        (display "Too high!\n")
                        (inc! guesses)
                    ]
                    [else
                        (display "You got it!\n")
                        (set! break #t)
                    ]
                )
            )
        )
    )
    (display "***GAME OVER***\n")
)

(randomize)
(guess)
(exit)

chez-compile.zsh, with my thanks to Graham Watt for explaining wpo and libraries:

#!/bin/zsh
if [ $# -ne 1 ]; then
    echo "Usage: chez-compile.zsh MAINNAME"
    exit 1
fi
rm -f *.so
rm -f *.wpo
mkdir -p bin

cat <<ENDTEXT |scheme -q --optimize-level 3
(compile-imported-libraries #t)
(generate-wpo-files #t)
(compile-program "$1.ss")
(compile-whole-program "$1.wpo" "bin/$1")
ENDTEXT

rm -f *.so
rm -f *.wpo
if [ -f "bin/$1" ]; then
    chmod 755 "bin/$1"
fi

Now I just:

% chez-compile.zsh guess
compiling guess.ss with output to guess.so
compiling stdlib.ss with output to stdlib.so
((stdlib))
()
% bin/guess
I'm thinking of a number from 1 to 100, try to guess it!
Guess #1? 50
Too low!
Guess #2? ^DBye!

Well, that was an adventure to get the equivalent of my first BASIC program from 1980, which can be run in Chipmunk BASIC if you don't happen to have a TRS-80 Model I handy:

1 REM GUESS. COPYRIGHT (C) 1980,2018 BY MARK DAMON HUGHES. ALL RIGHTS RESERVED.
5 RANDOMIZE INT(TIMER()):FOR I=1 TO 10:A=RND(1):NEXT I:REM CHIPMUNK'S RANDOMIZE SUCKS
10 N=INT(100*RND(1))+1:T=1
20 PRINT "I'M THINKING OF A NUMBER FROM 1 TO 100, TRY TO GUESS IT!"
100 PRINT "GUESS #";T;"? ";:INPUT "",G
110 IF G<=0 OR G>=100 OR G<>INT(G) THEN 200
120 IF G<N THEN 210
130 IF G>N THEN 220
140 GOTO 230
200 PRINT "TRY A NUMBER FROM 1 TO 100.":GOTO 100
210 PRINT "TOO LOW!":T=T+1:GOTO 100
220 PRINT "TOO HIGH!":T=T+1:GOTO 100
230 PRINT "YOU GOT IT!":PRINT "*** GAME OVER ***"
240 EXIT:REM CHIPMUNK

But now I can think about more complex problems in Chez Scheme!

Here's the tiniest piece of what I've been thinking about next:

Island in emoji

Fake Emoji & "Smart" Quotes Fuck Off

WordPress does a lot of things, not always well, but better than other blog platforms. But occasionally it runs amok like a toddler on espresso scribbling over your stuff with crayons and shitting in corners before falling down in a huff.

Today, it decided to replace my emoji with terrible little pictures again despite using the Disable Emojis plugin, so I gave up and edited functions.php (Appearance, Editor). And took this opportunity to uneducate my quotes so you can actually use code I paste without having to run it thru BBEdit's "straighten quotes" text menu. I didn't invent any of this, but it's all buried in obsolete version advice.

Add this:

// Fake Emoji & "Smart" Quotes Fuck Off
remove_action('wp_head', 'print_emoji_detection_script', 7);
remove_action('wp_print_styles', 'print_emoji_styles');
remove_action('admin_print_scripts', 'print_emoji_detection_script');
remove_action('admin_print_styles', 'print_emoji_styles');
remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
remove_filter('the_content_feed', 'wp_staticize_emoji');
remove_filter('comment_text_rss', 'wp_staticize_emoji');
add_filter('emoji_svg_url', '__return_false');
add_filter('run_wptexturize', '__return_false');

Sláinte 🥃

The Freedom to Write Garbage

Nice notebooks make it hard to write, because you feel you can't write garbage in them. Cheap notebooks give you the freedom to write garbage.

I used to use a DayRunner®, but then I'd fill it with cheap lined filler paper, and fill those pages with everything, and throw them in a box when I was done; I had a Palm Pilot for scheduling, but writing long notes in it was hard. Later I got into nice big Moleskines, which I rarely used because they seemed too nice for my chaos. Then stacks of Field Notes, which I keep one in my jacket, but use grudgingly. Lately I'm back to cheap 50¢ spiral notebooks and pads, and I write all the time.

I don't use a fancy pen, either. I'm currently using a "TÜL" mechanical pencil which is semi-junky (the eraser holder is loose and pushes down, so I have to tape it in place!) but fat enough for my hand, and a Field Notes clicky ballpoint which needs a 10¢ refill soon. A box of disposable ballpoints will work just as well as your $500 handcrafted engraved gold-plated fountain pen and hand-squeezed squid ink.

None of what I write on paper is important long-term, that's what Markdown files on a computer are for. Instead, every new topic or date gets written at the top of a page, sometimes just one item on that topic. And I'll go forward thru a book until it's full, then move on to the next. Anything worth keeping gets typed in eventually.

Here's a least-embarassing page from my current notepad, tracking Animal Crossing characters because paging thru Contacts in the game is so very slow.

notebook-animal crossing

And yes, my handwriting is appalling and random-looking; no two letter forms are the same. I learned slow and perfect block lettering until I got the Palm Pilot, then Graffiti retrained me and it's been crap ever since. I learned cursive as a kid but have never used it for long writing.

MARK-13 Thursday Music

Just discovered that iTunes Store has Hardware (Richard Stanley, 1990) for $9.99 in HD! Blu-Ray is out of print, DVD was a trash VHS port. Best punk rock SF apocalypse flick. "It's horrible, I love it!"

One rewatch later…

Looks even more prophetic now than when young cyberpunk Mark would watch the VHS over and over on a shitty CRT. Get used to the world of ecological disaster, locked into your apt by security systems installed by panopticon-watching perverts, terrorists with trucks smashing open buildings, people squatting & scavenging in the ruins. When a billion people near the uninhabitable equator starve and try to migrate away from global warming, you think anyone's gonna take them in? Or will we just irradiate the planet fighting wars, sterilize the mutant population, and let Google's autonomous killer robots knock us down to carrying capacity? I never believed in a future, and here it is.

… So, that went a little dark. Have a drink, a joint, a fuck, make some art, and play some cute distracting videogames to forget about the end of the world.

The soundtrack's another OOP classic, but here's the day's music based on it:

On the Use of Communications Networks of Tin Cans and String

Gen-X (remember us? I suppose not.) invented l33tspeek but knew when to stop doing it. Now, "old people" use ellipses… and end texts with a period, and it makes the Millennials upset.

Back in the day, all we had were tin cans & string, er, BBS's, which were like Thunderdome without the sense of fair play, but we also tolerated much weirder behavior, and at worst you couldn't call back to one board in your city. Now if you say anything any one of the kiddie mafia doesn't like, they all have a toddler meltdown at you, over the tin can network we built.

I don't have a solution for any of this, just kicking out Tiny Tim's crutches and laughing at him, as one does this season.

Advent of Code 2017: Week 1

After 7 days, let's see how my Advent of Code 2017 is going.

  • Web framework: I made a standard web console, which I then copy forward to the next day. I could just as easily have put all the buttons on a single page, but it'd get too long by day 31.

  • Unit testing: As seen in stdlib.js, my test framework is very simple: On page load, setup, run some asserts, finish to get stats and redbar/greenbar the test console. This has been a great win, even though several of the days had only one or two examples.

  • 01:

    "The captcha requires you to review a sequence of digits (your puzzle input) and find the sum of all digits that match the next digit in the list. The list is circular, so the digit after the last digit is the first digit in the list."

    • So this is nice and easy, linear problem for JS string processing. On the second variant I turned the inner function to pick the next char into a function, which I pass in. No external state.
  • 02:

    "The spreadsheet consists of rows of apparently-random numbers. To make sure the recovery process is on the right track, they need you to calculate the spreadsheet's checksum. For each row, determine the difference between the largest value and the smallest value; the checksum is the sum of all of these differences."

    • Needed to write more utils, this time to process strings into a table of numbers. My first solution kept it as a table of strings, and then I had problems with JS type coercion. Same strategy of finding a pure functional inner loop and extracting that as a function. Actually, subtotalFunc1 mutates cols, but you'll never use it again so it doesn't matter.
  • 03:

    "Each square on the grid is allocated in a spiral pattern starting at a location marked 1 and then counting up while spiraling outward. For example, the first few squares are allocated like this:

    17 16 15 14 13
    18 5 4 3 12
    19 6 1 2 11
    20 7 8 9 10
    21 22 23---> ...

    While this is very space-efficient (no squares are skipped), requested data must be carried back to square 1 (the location of the only access port for this memory system) by programs that can only move up, down, left, or right. They always take the shortest path: the Manhattan Distance between the location of the data and square 1."

    • This was a little insane. There's apparently a pure math solution, but I am not a mathematician, I am a turtle; also song; also Lewis Carroll; also Gödel Escher Bach. So I solved it by moving a turtle around the spiral, turning left whenever there's an open space, and then I could just examine the spiral for values.
    • Yes, I store points as a vector-2 of numbers, rather than making a "Point" class with x,y. In C using a struct makes more sense (since it only takes 8 bytes intead of the 40+ in any object system!), but in anything else, the vector requires the least memory, least work, and is easiest to serialize, use as a hashtable key, and so on.
    • I just copy-pasted and modified for the second task, instead of making a nice inner function to pass in. It's not incredibly hard to parameterize, but I would need to break out of that inner function to return early, so probably using an exception for flow control?
    • Javascript objects are super-useful, like Python dicts but even easier to use. Stuff a bunch of points for the grid, and properties for the minPt, maxPt (bounds) and lastPt, one structure gets to hold everything about the spiral. Writing this in a type-safe or pure functional language would be annoying.
  • 04:

    "A passphrase consists of a series of words (lowercase letters) separated by spaces.
    To ensure security, a valid passphrase must contain no duplicate words."

    • Super simple, a histogram that only counts to 1. The second part only requires sorting the chars in each word, so you can check they aren't anagrams. I was worried that day 3 was the start of an exponential curve in difficulty, so by the end it'd take to the end of the Earth to finish.
  • 05:

    "The message includes a list of the offsets for each jump. Jumps are relative: -1 moves to the previous instruction, and 2 skips the next one. Start at the first instruction in the list. The goal is to follow the jumps until one leads outside the list.
    In addition, these instructions are a little strange; after each jump, the offset of that instruction increases by 1. So, if you come across an offset of 3, you would move three instructions forward, but change it to a 4 for the next time it is encountered.
    How many steps does it take to reach the exit?"

    • I returned the program counter at first, and that passes the sample data test, so I "guessed" wrong the first time, but then reread and found my bug. Having only one example is a problem. Both parts are as usual solved by passing in a function to determine the next state of the instruction. Trivial one aside from my stupid bug.
    • I'm building up a good library of tools by now.
  • 06:

    "In each cycle, it finds the memory bank with the most blocks (ties won by the lowest-numbered memory bank) and redistributes those blocks among the banks. To do this, it removes all of the blocks from the selected bank, then moves to the next (by index) memory bank and inserts one of the blocks. It continues doing this until it runs out of blocks; if it reaches the last memory bank, it wraps around to the first one.
    The debugger would like to know how many redistributions can be done before a blocks-in-banks configuration is produced that has been seen before."

    • Making the balancer was straightforward, I stored each state as a string in a history array, and a parallel histogram to catch the duplicate; I could get rid of hist but it turned out to make the second task easier.
  • 07: (Don't read this or the code until tomorrow if you don't want a spoiler)

    "You offer to help, but first you need to understand the structure of these towers. You ask each program to yell out their name, their weight, and (if they're holding a disc) the names of the programs immediately above them balancing on that disc. You write this information down (your puzzle input). Unfortunately, in their panic, they don't do this in an orderly fashion; by the time you're done, you're not sure which program gave which information.
    Before you're ready to help them, you need to make sure your information is correct. What is the name of the bottom program?"

    • Building the tree was a good puzzle, but not hard: Parse the text into nodes, keep them in a dictionary, then build the structure, and return the only parentless node (the root). Making a recursive toString so I could debug it was important…
    • Second task looked to be tedious (depth-first search and pass the result all the way up), but then I just looked at my output and saw the unbalanced numbers, so entered it by hand. I am a computer, too.

I've got both gold stars each day. I'm completely incapable of reliably checking in at exactly 21:00 PST, and I'm too fussy about my code to ever be "the fastest", so my rankings are awful; maybe it ought to count from when you read the problem set, but then everyone would cheat on at least the first task.

Advent of Code 2017

I've joined the Advent of Code, and I'll be doing it in JS. Got my 2 stars for the day.

For good discipline (or as a handicap), I'm building a halfway-decent set of pages, unit testing framework, and sort of doing things right (good ES6 practices) instead of easy (hack some inline JS in compatibility mode). I'll link it in the sidebar tomorrow, when challenge 1 expires: My Advent of Code

Go on and do it yourself! I say "easy mode is for babies" and make it hard on myself, but really you can do this in anything. There's a perfectly nice Chipmunk BASIC or FreePascal if you're old-school.

Note: The leaderboard reset time is ridiculous, and I don't care about speed-coding or leaderboards. Don't stress about competing for first 100 completions, just do the thing.

Animal Crossing: Pocket Camp

On December, 2005, Animal Crossing: Wild World (AC:WW) came out for the Nintendo DS, I immediately got it, created a town called Yama, and played at least 15 minutes, often hours, every day for years.

I finally got to the point where my house was maxed out, filled with trophies, mail storage was being used for excess items, I had every K.K.Slider song, most of my town was covered with tree farms, flower farms, or stickers to make roads (and force digging holes to be in specific areas).

I was spending too much time in Yama, and couldn't stop checking it every day, so around 2008 I deleted my town, I couldn't break the habit any other way. I still miss Yama.

Animal Crossing: Wild World Snowman

Now Animal Crossing Pocket Camp (AC:PC) is out on iOS, and I can see I'll have the same problem eventually, but for now it's nice to have anything like it back.

AC:PC Squirrel

It's a little frustrating at first, being forced to do tutorials with no freedom of action; I'm of the theory that a game should have chargen, and then you're loose in the world. If you want tutorials or information, leave NPCs or books around, but don't make the player tap on a person or box to progress. The IAP doesn't seem too aggressive in this, I know I could buy "leaf tickets" (and I have to have them, to get K.K.Slider and Tom Nook for my camp), but I'm building them up at some pace in-game.

The camp is much smaller than a town or expanded house, and I don't like the outdoors theme and hippie bus as much as AC:WW, and you can't alter other camps. There's a really annoying siren wail when you're dragging items around, it's piercing and the least Nintendo-like sound I've ever heard; I'm going to try to file a bug report if they have any way to do that. As usual, Nintendo has never heard of scaling servers, and I'm getting constant errors, which will only get worse as more of the world wakes up and starts playing today.

AC:PC Communications Error

But problems aside, I live there now. If you want to be my friend 😳, my ID is: 1227 6011 048