Something went wrong. Try again.
A programming language for the TRS-80 Color Computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899# 03: Keyboard input and an entity state machine.## Exercises: sum types, `match` with exhaustiveness, payload-carrying# variants, tuple scrutinees, nested matching, `if` as an expression.## Run it and hold the arrow keys or space: the cell in the middle of# the screen shows the player state (S = standing, U/D/L/R = walking,# * = shooting).
type Direction = Up | Down | Left | Right
# Variants may carry payloads. There is no null: "no input" is a# variant like any other.type Input = | Idle | Move(Direction) | Fire
type PlayerState = | Standing | Walking(Direction) | Shooting { cooldown: Uint8 } # variants can have named fields
# PIA0 drives the keyboard matrix: write a column-select mask to port# B (selected column low), then read port A for that column's row# states, active low.var pia0_a: Uint8 at 0xFF00var pia0_b: Uint8 at 0xFF02
# The text screen, for showing the current state.var screen: [Uint8; 512] at 0x0400
# Row 8, column 12: roughly the middle of the 32x16 text screen.const STATE_CELL: Uint16 = 8 * 32 + 12
# The arrow keys and space all sit on matrix row 3 (bit 3), in# columns 3..7.fn row3_down(column_mask: Uint8) -> Bool { pia0_b = column_mask pia0_a & 0x08 == 0}
fn read_input() -> Input { var keys: Uint8 = 0 if row3_down(0xF7) { keys = keys | 0x01 } # up arrow (column 3) if row3_down(0xEF) { keys = keys | 0x02 } # down arrow (column 4) if row3_down(0xDF) { keys = keys | 0x04 } # left arrow (column 5) if row3_down(0xBF) { keys = keys | 0x08 } # right arrow (column 6) if row3_down(0x7F) { keys = keys | 0x10 } # space bar (column 7)
# `match` is an expression and must be exhaustive. `_` is the # catch-all. Matching on integer values is allowed for Uint8/Int8. match keys & 0x0F { 0x01 => Move(Up), 0x02 => Move(Down), 0x04 => Move(Left), 0x08 => Move(Right), _ => if keys & 0x10 != 0 { Fire } else { Idle }, }}
fn step(state: PlayerState, input: Input) -> PlayerState { match (state, input) { # Tuple matching lets the whole transition table read flat. (Shooting { cooldown }, _) => if cooldown == 0 { Standing } else { Shooting { cooldown: cooldown - 1 } },
(_, Fire) => Shooting { cooldown: 12 }, (_, Move(dir)) => Walking(dir), (Walking(_), Idle) => Standing, (Standing, Idle) => Standing, }}
fn show(state: PlayerState) { # VDG glyph codes 0x00..0x3F are inverse video. screen[STATE_CELL] = match state { Standing => 0x13, # S Walking(dir) => match dir { Up => 0x15, # U Down => 0x04, # D Left => 0x0C, # L Right => 0x12, # R }, Shooting { cooldown } => 0x2A, # * }}
fn main() { for var cell in screen { cell = 0x60 } # clear to VDG spaces
var state: PlayerState = Standing loop { state = step(state, read_input()) show(state) }}