Skip to content

Instantly share code, notes, and snippets.

@gabrieloc
Created October 2, 2020 21:20
Show Gist options
  • Save gabrieloc/a14eaff9e2582cc27fdf5521b00d7f21 to your computer and use it in GitHub Desktop.
Save gabrieloc/a14eaff9e2582cc27fdf5521b00d7f21 to your computer and use it in GitHub Desktop.

Revisions

  1. gabrieloc created this gist Oct 2, 2020.
    188 changes: 188 additions & 0 deletions guy.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,188 @@
    import Foundation
    import simd

    enum Input: String, CaseIterable {
    case up = ""
    case right = ""
    case down = ""
    case left = ""

    var bytes: [UInt8] {
    switch self {
    case .up: return [27, 91, 65]
    case .right: return [27, 91, 67]
    case .down: return [27, 91, 66]
    case .left: return [27, 91, 68]
    }
    }

    init?(_ bytes: [UInt8]) {
    guard let match = Self.allCases.first(where: { $0.bytes == bytes }) else {
    return nil
    }
    self = match
    }
    }

    class InputHandler {
    static func initStruct<S>() -> S {
    let struct_pointer = UnsafeMutablePointer<S>.allocate(capacity: 1)
    let struct_memory = struct_pointer.pointee
    struct_pointer.deallocate()
    return struct_memory
    }

    static func enableRawMode(fileHandle: FileHandle) -> termios {
    var raw: termios = initStruct()
    tcgetattr(fileHandle.fileDescriptor, &raw)

    let original = raw

    raw.c_lflag &= ~(UInt(ECHO | ICANON))
    tcsetattr(fileHandle.fileDescriptor, TCSAFLUSH, &raw);

    return original
    }

    static func restoreRawMode(fileHandle: FileHandle, originalTerm: termios) {
    var term = originalTerm
    tcsetattr(fileHandle.fileDescriptor, TCSAFLUSH, &term);
    }

    let originalTerm: termios

    init() {
    self.originalTerm = Self.enableRawMode(
    fileHandle: FileHandle.standardInput
    )
    }

    deinit {
    Self.restoreRawMode(
    fileHandle: FileHandle.standardInput,
    originalTerm: originalTerm
    )
    }

    var input: Input?
    }

    class Renderer {
    var buffer: [Character]
    var length: Int { buffer.count }
    let size: simd_int2

    init(size: simd_int2) {
    self.size = size
    self.buffer = Array<Character>(
    repeating: "",
    count: Int(size.x * size.y)
    )
    }

    func scale(_ position: simd_float2) -> simd_int2 {
    simd_int2(
    simd_int2.Scalar(position.x * Float(size.x)),
    simd_int2.Scalar(position.y * Float(size.y))
    )
    }

    func drawChar(_ char: Character, position: simd_float2) {
    let i = scale(position).index(rowLength: size.x)
    self.buffer[i % length] = char
    }

    func drawRect(frame: float2x2, char: Character) {
    let p1 = convertViewportPoint(frame.columns.0)
    let p2 = convertViewportPoint(frame.columns.0 + frame.columns.1)

    (p1.x..<p2.x).forEach { x in
    (p1.y..<p2.y).forEach { y in
    let i = Int(simd_int2(x: x, y: y).index(rowLength: size.x))
    self.buffer[i % length] = char
    }
    }
    }

    func error() {
    buffer = Array<Character>(repeating: "", count: length)
    }

    func clear() {
    buffer = Array<Character>(repeating: "", count: length)
    }

    func blit() {
    print("\u{001B}[2J")
    print(
    String (
    stride(from: 0, to: length, by: Int(size.x)).map { i in
    String(buffer[i..<(i + Int(size.x))])
    }.joined(separator: "\n")
    )
    )
    }

    func convertViewportPoint(_ point: simd_float2) -> simd_int2 {
    simd_int2(
    x: Int32(Float(size.x) * point.x),
    y: Int32(Float(size.y) * point.y)
    )
    }
    }

    extension simd_int2 {
    func index(rowLength: Scalar) -> Int {
    Int((y * rowLength) + (x % rowLength))
    }
    }

    class Game {
    let inputHandler = InputHandler()
    let renderer = Renderer(
    size: simd_int2(x: 40, y: 10)
    )
    var isRunning = false
    var pointer = simd_float2(0.5, 0.5)
    let speed: Float = 0.1
    let guy = Character("☃︎")

    func start() {
    var data: Data
    repeat {
    data = FileHandle.standardInput.availableData
    let bytes = [UInt8](data)
    let input = Input(bytes)
    renderer.clear()
    if let input = input {
    switch input {
    case .up:
    pointer -= simd_float2(0, speed)
    case .right:
    pointer += simd_float2(speed, 0)
    case .down:
    pointer += simd_float2(0, speed)
    case .left:
    pointer -= simd_float2(speed, 0)
    }
    pointer.clamp(
    lowerBound: simd_float2.zero,
    upperBound: simd_float2.one
    )
    }
    renderer.drawChar(
    guy,
    position: pointer
    )
    renderer.blit()
    } while (data.count > 0)
    }

    func stop() {
    isRunning = false
    }
    }


    let game = Game()
    game.start()