Skip to content

Instantly share code, notes, and snippets.

@gabrieloc
Created October 2, 2020 21:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gabrieloc/a14eaff9e2582cc27fdf5521b00d7f21 to your computer and use it in GitHub Desktop.
Save gabrieloc/a14eaff9e2582cc27fdf5521b00d7f21 to your computer and use it in GitHub Desktop.
Terminal game
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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment