Wake up Neo…

I was doing some cleanup on my computer and came across an old program I had made in 2001 while I was still a young ‘un. Let’s see what it does…

That’s actually pretty cool! Time to ship it to production, but first let’s give it the ol’ PR treatment.

Sort of a picky PR person! Let’s address some of these points.

  1. Email does not work
    • Blame Lord Black
  2. Alphabetize headers please
    • Agree…
  3. What do these do?
    • I didn’t know what I was doing…
  4. What do these do and what do the numbers mean?
    • Well the ‘5’ is obviously the length of string one…
  5. Ditto
    • Ditto!
  6. Blah blah blah…

Sort of a jerk, that PR person…

Some points were raised though. I could go back and fix them but that means going and installing Turbo-C++… or trying to modernize it…

I’m an iOS dev now… this console based stuff is outside of my wheelhouse but I don’t have a clue how I’d put char ‘Z’ at point (X, Y) in the terminal. It’d be fun to learn how to do so. There’s this library called ncurses I’ve heard about. So let’s modernize it!

So we create a swift macOS command line app, link our binary against libncurses.tbd and… what the heck do I import?

Some googling and I come across this super helpful video: “https://www.youtube.com/watch?v=syCz6CmzTN8”. Cool we’ve got a hello world up on our screen!

This looks like we could almost do a straight port of the code above… lets see how close we can get.

Pretty close! Here’s how it looks:

And here’s the code:

import Darwin.ncurses

var string_one_length: Int32 = 5
var string_two_length: Int32 = 7
var string_three_length: Int32 = 9
var string_four_length: Int32 = 12
var clear_string_one_length: Int32 = 5
var clear_string_two_length: Int32 = 5
var clear_string_three_length: Int32 = 5
var clear_string_four_length: Int32 = 5

var string_one_x: Int32 = 5
var string_one_y: Int32 = 7
var string_two_x: Int32 = 9
var string_two_y: Int32 = 12
var string_three_x: Int32 = 5
var string_three_y: Int32 = 7
var string_four_x: Int32 = 9
var string_four_y: Int32 = 4
var clear_string_one_x: Int32 = 5
var clear_string_one_y: Int32 = 5
var clear_string_two_x: Int32 = 5
var clear_string_two_y: Int32 = 5
var clear_string_three_x: Int32 = 5
var clear_string_three_y: Int32 = 5
var clear_string_four_x: Int32 = 5
var clear_string_four_y: Int32 = 5


initscr()
start_color()
noecho()
curs_set(0)
init_pair(1, Int16(COLOR_GREEN), Int16(COLOR_BLACK))
clear()

while(true) {
    attron(COLOR_PAIR(1))
    clear_string_one();
    string_one();
    clear_string_two();
    string_two();
    clear_string_three();
    string_three();
    clear_string_four();
    string_four();
    attroff(COLOR_PAIR(1))
    refresh()
    usleep(10000)
}

func string_one() {

    let string_one_char =  UInt32.random(in: 0..<229) + 27;
    move(string_one_y, string_one_x) // Watch out! These are switched!
    addch(string_one_char)

    string_one_length = string_one_length - 1;
    string_one_y = string_one_y + 1;

    if (string_one_length == 0) {
        string_one_length = Int32.random(in: 0..<10) + 4;
        string_one_x = Int32.random(in: 0..<80);
        string_one_y = Int32.random(in: 0..<39);
    }
}

func string_two() {

    let string_two_char =  UInt32.random(in: 0..<229) + 27;
    move(string_two_y, string_two_x)
    addch(string_two_char)

    string_two_length = string_two_length - 1;
    string_two_y = string_two_y + 1;

    if (string_two_length == 0) {
        string_two_length = Int32.random(in: 0..<10) + 4;
        string_two_x = Int32.random(in: 0..<80);
        string_two_y = Int32.random(in: 0..<39);
    }
}

func string_three() {

    let string_three_char =  UInt32.random(in: 0..<229) + 27;
    move(string_three_y, string_three_x)
    addch(string_three_char)

    string_three_length = string_three_length - 1;
    string_three_y = string_three_y + 1;

    if (string_three_length == 0) {
        string_three_length = Int32.random(in: 0..<10) + 4;
        string_three_x = Int32.random(in: 0..<80);
        string_three_y = Int32.random(in: 0..<39);
    }
}

func string_four() {

    let string_four_char =  UInt32.random(in: 0..<229) + 27;
    move(string_four_y, string_four_x)
    addch(string_four_char)

    string_four_length = string_four_length - 1;
    string_four_y = string_four_y + 1;

    if (string_four_length == 0) {
        string_four_length = Int32.random(in: 0..<10) + 4;
        string_four_x = Int32.random(in: 0..<80);
        string_four_y = Int32.random(in: 0..<39);
    }
}

func clear_string_one() {
    move(clear_string_one_y, clear_string_one_x)
    addch(UInt32(" "))

    clear_string_one_length = clear_string_one_length - 1;
    clear_string_one_y = clear_string_one_y + 1;

    if (clear_string_one_length == 0) {
        clear_string_one_length = Int32.random(in: 0..<10) + 4;
        clear_string_one_x = Int32.random(in: 0..<80);
        clear_string_one_y = Int32.random(in: 0..<39);
    }
}

func clear_string_two() {
    move(clear_string_two_y, clear_string_two_x)
    addch(UInt32(" "))

    clear_string_two_length = clear_string_two_length - 1;
    clear_string_two_y = clear_string_two_y + 1;

    if (clear_string_two_length == 0) {
        clear_string_two_length = Int32.random(in: 0..<10) + 4;
        clear_string_two_x = Int32.random(in: 0..<80);
        clear_string_two_y = Int32.random(in: 0..<39);
    }
}

func clear_string_three() {
    move(clear_string_three_y, clear_string_three_x)
    addch(UInt32(" "))

    clear_string_three_length = clear_string_three_length - 1;
    clear_string_three_y = clear_string_three_y + 1;

    if (clear_string_three_length == 0) {
        clear_string_three_length = Int32.random(in: 0..<10) + 4;
        clear_string_three_x = Int32.random(in: 0..<80);
        clear_string_three_y = Int32.random(in: 0..<39);
    }
}

func clear_string_four() {
    move(clear_string_four_y, clear_string_four_x)
    addch(UInt32(" "))

    clear_string_four_length = clear_string_four_length - 1;
    clear_string_four_y = clear_string_four_y + 1;

    if (clear_string_four_length == 0) {
        clear_string_four_length = Int32.random(in: 0..<10) + 4;
        clear_string_four_x = Int32.random(in: 0..<80);
        clear_string_four_y = Int32.random(in: 0..<39);
    }
}

Now let’s finally address those PR comments…

//
//  main.swift
//  matrixTwoDotOh
//
//  Created by Daniel Drzimotta on 2020-05-07.
//  Copyright © 2020 Daniel Drzimotta. All rights reserved.
//

import Darwin.ncurses

struct NCursesPoint {
    var x: Int32
    var y: Int32
}

struct NCursesSize {
    var width: Int32
    var height: Int32
}

protocol MatrixString {
    var position: NCursesPoint { get }
    var length: Int32 { get }
    var displayCharacter: UInt32 { get }

    mutating func update()
}

let initialLengthFunc = { Int32.random(in: 0..<10) + 4 }

// TODO: Would be nice to get this working! I have no clue how though
// With help from: https://scifi.stackexchange.com/a/182823
// I think I need ncursesw... Could I statically link it? Let's use just the
// characters on our keyboard for now...
//let characterSet = "日ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ012345789Z:・.\"=*+-<>¦|╌çリク"
let characterSet = "012345789ABCDEFGHIJKLMNOPQRSTUVWXYZ`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?"

extension MatrixString {
    func draw() {
        mvaddch(position.y, position.x, displayCharacter)
    }

    static var initialLength: Int32 {
        Int32.random(in: 0..<10) + 4
    }
}

struct MatrixCharacterString: MatrixString {
    var position: NCursesPoint
    var maxPosition: NCursesSize
    
    var length: Int32
    var displayCharacter: UInt32 = UInt32(" ")

    init(maxPosition: NCursesSize) {
        self.position = NCursesPoint(x: Int32.random(in: 0..<maxPosition.width), y: Int32.random(in: 0..<maxPosition.height) - 1)
        self.maxPosition = maxPosition

        self.length = initialLengthFunc()
    }

    mutating func update() {
        displayCharacter = characterSet.randomElement()?.unicodeScalars.first?.value ?? 0
        length -= 1
        position.y += 1

        if length < 0 {
            self = MatrixCharacterString(maxPosition: maxPosition)
        }
    }
}

struct MatrixClearString: MatrixString {
    var position: NCursesPoint
    var maxPosition: NCursesSize

    var length: Int32
    var displayCharacter: UInt32 = UInt32(" ")

    init(maxPosition: NCursesSize) {
        self.position = NCursesPoint(x: Int32.random(in: 0..<maxPosition.width), y: Int32.random(in: 0..<maxPosition.height) - 1)
        self.maxPosition = maxPosition

        self.length = initialLengthFunc()
    }

    mutating func update() {
        length -= 1
        position.y += 1

        if length == 0 {
            self = MatrixClearString(maxPosition: maxPosition)
        }
    }
}

initscr()

var screenSize = NCursesSize(width: getmaxx(stdscr), height: getmaxy(stdscr))

var matrixStrings: [MatrixString] = [
    MatrixCharacterString(maxPosition: screenSize),
    MatrixCharacterString(maxPosition: screenSize),
    MatrixCharacterString(maxPosition: screenSize),
    MatrixCharacterString(maxPosition: screenSize),
    MatrixClearString(maxPosition: screenSize),
    MatrixClearString(maxPosition: screenSize),
    MatrixClearString(maxPosition: screenSize),
    MatrixClearString(maxPosition: screenSize)
]

let waitForCharacterTimeout: Int32 = 50

start_color()
noecho()
timeout(waitForCharacterTimeout);
curs_set(0)
init_pair(1, Int16(COLOR_GREEN), Int16(COLOR_BLACK))
clear()
attron(COLOR_PAIR(1))

while(getch() == ERR) {
    for i in matrixStrings.indices {
        matrixStrings[i].draw()
        matrixStrings[i].update()
    }

    refresh()
}

Cool! Turns out the chars that we were displaying in the DOS version are all over the place when using ncurses. (https://en.wikipedia.org/wiki/Code_page_437) I looked at displaying the original matrix characters but the version of ncurses included with macOS doesn’t seem to support them. I just decided to display the characters that I see on my keyboard. So how does it look?

Not bad! This was a fun little project.