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?
![](https://www.csdi.io/wp-content/uploads/2020/05/2.gif)
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.