Matrix on Linux

Expanding on my previous blog post, I wanted to get those cool characters displaying in my ncurses from DOS port. Namely, these characters:

日ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ012345789Z:・.\"=*+-<>¦|╌çリク

According to a well informed stack exchange post at: https://scifi.stackexchange.com/a/182823 those are the characters you’d see on the matrix screen. They encode the 👱‍♀️, 👨🏽‍⚕️and 👩‍🦰.

To display characters wider than 8 bits in ncurses you need a… wait for it… wide ncurses, namely ncursesw.

I tried doing this on macOS, but I couldn’t figure out how to get an installed version of ncursesw to play nicely with the already bundled version.

Luckily Swift runs on Ubuntu! So we installed Ubuntu 18.04, installed some dependencies ($ sudo apt install libncurses5-dev and $ sudo apt install libncursesw5-dev) and we were off to the races.

I made a nice Swift package for this at https://github.com/csdiweb/CNCURSESW. I used that as a dependency like so:

// swift-tools-version:5.2
import PackageDescription

let package = Package(
  name: "matrixTwoDotOh",
  products: [
    .executable(name: "matrixTwoDotOh", targets: ["matrixTwoDotOh"])
  ],
  dependencies: [
    .package(url:  "https://github.com/csdiweb/CNCURSESW", from: "1.0.0")
  ],
  targets: [
    .target(
      name: "matrixTwoDotOh",
      dependencies: [])
  ]
)

Now we can just do a import CNCURSESW_LINUX in our code. Here is our updated source, now running on Ubuntu:

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

import Glibc
import CNCURSESW_LINUX

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: Character { get }

    mutating func update()
}

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

let characterSet = "日ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ012345789Z:・.\"=*+-<>¦|╌çリク"

// With help from: https://stackoverflow.com/questions/49451164/convert-swift-string-to-wchar-t
extension String {
    /// Calls the given closure with a pointer to the contents of the string,
    /// represented as a null-terminated wchar_t array.
    func withWideChars<Result>(_ body: (UnsafePointer<wchar_t>) -> Result) -> Result {
        let u32 = self.unicodeScalars.map { wchar_t(bitPattern: $0.value) } + [0]
        return u32.withUnsafeBufferPointer { body($0.baseAddress!) }
    }
}

extension MatrixString {
    func draw() {
        var special: cchar_t = cchar_t()

        String(displayCharacter).withWideChars { asWideChars in
            setcchar(&special, asWideChars, WA_NORMAL, 1, nil)
            mvadd_wch(position.y, position.x, &special)
        }
    }

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

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

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

    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() ?? Character(" ")
        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: Character = Character(" ")

    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)
        }
    }
}

setlocale (LC_ALL, "")
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()

var lastChar: Int32 = -1
while(lastChar != "x".utf8CString[0]) {
    for i in matrixStrings.indices {
        matrixStrings[i].draw()
        matrixStrings[i].update()
    }

    refresh()
    lastChar = getch()
}

endwin()

How does it look?

Pretty dang cool. This has been an itch I’ve wanted to scratch for a while and now I’ve learned how to do this, plus in Swift, and on Linux.