this is literal garbage
This commit is contained in:
commit
355811be3a
6 changed files with 349 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
16
Cargo.lock
generated
Normal file
16
Cargo.lock
generated
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "owo-colors"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "worm"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"owo-colors",
|
||||||
|
]
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "worm"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
owo-colors = "3.5.0"
|
21
README.md
Normal file
21
README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# sandworm
|
||||||
|
program space is an arbitrary sized grid
|
||||||
|
the worm starts where you place the worm head @
|
||||||
|
as it passes over commands, they get moved to the back of the worm (also depending on the worm length)
|
||||||
|
commands to change direction <>^v
|
||||||
|
values get pushed to stack (eaten) when passed over, worm body length increases
|
||||||
|
autosarkophagy/self-cannibalism is possible, cutting off the back of the worm (the lower part of the stack) and pushing the passed value to the stack (front of worm)
|
||||||
|
conditional: pop then side/forward if top of stack is zero
|
||||||
|
|
||||||
|
## commands
|
||||||
|
```
|
||||||
|
+- pop 2 values *, push sum/difference (uses the order they are popped, so `0 -` negates the top of the stack)
|
||||||
|
><^v change direction
|
||||||
|
0123456789ABCDEF push number to stack
|
||||||
|
/\ pop stack, reflect to the side if zero
|
||||||
|
? reads one byte of input
|
||||||
|
= duplicate top of stack
|
||||||
|
! pop and write output as ascii char
|
||||||
|
" pop and write output as number
|
||||||
|
```
|
||||||
|
* pop when stack is empty yields zero
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
hard_tabs = true
|
301
src/main.rs
Normal file
301
src/main.rs
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
use std::{env, fs, io::stdin, process::exit};
|
||||||
|
|
||||||
|
use owo_colors::OwoColorize;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SandWormInterpreter {
|
||||||
|
program: Vec<Vec<u8>>,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
/// worm body locations
|
||||||
|
worm: Vec<(usize, usize)>,
|
||||||
|
worm_head: (usize, usize),
|
||||||
|
/// queue for outputting commands at the back of the worm
|
||||||
|
worm_out: Vec<u8>,
|
||||||
|
worm_in: Vec<u8>,
|
||||||
|
direction: Direction,
|
||||||
|
input: Vec<u8>,
|
||||||
|
input_index: usize,
|
||||||
|
output: Vec<u8>,
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
enum Direction {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
#[default]
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
enum State {
|
||||||
|
#[default]
|
||||||
|
Running,
|
||||||
|
EndOfProgram,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<_> = env::args().collect();
|
||||||
|
if args.len() <= 1 {
|
||||||
|
println!("usage: sandworm source_file input_file");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
let filename = &args[1];
|
||||||
|
let source = fs::read_to_string(filename).unwrap_or_else(|err| {
|
||||||
|
println!("Error reading file: {err}");
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
let input_data = args
|
||||||
|
.get(2)
|
||||||
|
.map(|path| {
|
||||||
|
fs::read(path).unwrap_or_else(|err| {
|
||||||
|
println!("Error reading file: {err}");
|
||||||
|
exit(1);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut interpreter = SandWormInterpreter::new(&source, input_data);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
interpreter.show();
|
||||||
|
let mut input_text = String::new();
|
||||||
|
stdin().read_line(&mut input_text).unwrap();
|
||||||
|
let action: Vec<_> = input_text.trim().split_ascii_whitespace().collect();
|
||||||
|
if input_text.starts_with("input ") {
|
||||||
|
interpreter.input.extend(&input_text.as_bytes()[6..]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match action.as_slice() {
|
||||||
|
[] | ["step"] => interpreter.step_once(),
|
||||||
|
["step", num] => _ = num.parse().map(|n| interpreter.step(n)),
|
||||||
|
// ["run"] => interpreter.run(),
|
||||||
|
["q" | "exit" | "quit"] => break,
|
||||||
|
|
||||||
|
_ => println!("{}", "unrecognised command".red()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SandWormInterpreter {
|
||||||
|
fn new(source: &str, input: Vec<u8>) -> Self {
|
||||||
|
let (program, start_pos) = parse(source);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
width: program[0].len(),
|
||||||
|
height: program.len(),
|
||||||
|
program,
|
||||||
|
worm: Vec::new(),
|
||||||
|
worm_head: start_pos,
|
||||||
|
worm_in: Vec::new(),
|
||||||
|
worm_out: Vec::new(),
|
||||||
|
input,
|
||||||
|
output: Vec::new(),
|
||||||
|
state: State::default(),
|
||||||
|
direction: Direction::default(),
|
||||||
|
input_index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self, n: usize) {
|
||||||
|
for _ in 0..n {
|
||||||
|
if self.state != State::Running {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.step_once();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&self) {
|
||||||
|
dbg!(&self);
|
||||||
|
println!(
|
||||||
|
"{:?}",
|
||||||
|
self.worm.iter().map(|p| self.get(*p)).collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
for (row, line) in self.program.iter().enumerate() {
|
||||||
|
for (col, &byte) in line.iter().enumerate() {
|
||||||
|
if self.worm.contains(&(col, row)) {
|
||||||
|
if byte < 10 {
|
||||||
|
print!("{:x}", byte.on_green());
|
||||||
|
} else {
|
||||||
|
print!("{}", "*".green().on_red());
|
||||||
|
}
|
||||||
|
} else if self.worm_head == (col, row) {
|
||||||
|
if byte == b'@' {
|
||||||
|
print!("{}", "@".on_yellow());
|
||||||
|
} else {
|
||||||
|
panic!("worm head corrupted");
|
||||||
|
}
|
||||||
|
} else if byte == 0 || byte == b' ' {
|
||||||
|
print!(" ");
|
||||||
|
} else if byte.is_ascii_alphanumeric() || byte.is_ascii_punctuation() {
|
||||||
|
print!("{}", byte as char);
|
||||||
|
} else {
|
||||||
|
print!("{}", "*".green());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
println!("output: {}", String::from_utf8_lossy(&self.output));
|
||||||
|
println!("input: {}", String::from_utf8_lossy(&self.input));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step_once(&mut self) {
|
||||||
|
if self.state != State::Running {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let front = self.front();
|
||||||
|
if front.0 >= self.width || front.1 >= self.height {
|
||||||
|
self.state = State::EndOfProgram;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let instruction = self.get(front);
|
||||||
|
let mut dont_push_instruction = false;
|
||||||
|
|
||||||
|
match instruction {
|
||||||
|
b'0'..=b'9' => {
|
||||||
|
self.worm_in.push(instruction - 48);
|
||||||
|
dont_push_instruction = true;
|
||||||
|
}
|
||||||
|
// b'0'..=b'9' => self.grow(instruction - 48),
|
||||||
|
b'+' => {
|
||||||
|
let a = self.shrink();
|
||||||
|
self.worm_out.push(instruction);
|
||||||
|
let b = self.shrink();
|
||||||
|
dont_push_instruction = true;
|
||||||
|
self.worm_in.push(a + b);
|
||||||
|
}
|
||||||
|
b'-' => {
|
||||||
|
let a = self.shrink();
|
||||||
|
self.worm_out.push(instruction);
|
||||||
|
dont_push_instruction = true;
|
||||||
|
let b = self.shrink();
|
||||||
|
self.worm_in.push(a + b);
|
||||||
|
}
|
||||||
|
b'v' => self.direction = Direction::Down,
|
||||||
|
b'^' => self.direction = Direction::Up,
|
||||||
|
b'<' => self.direction = Direction::Left,
|
||||||
|
b'>' => self.direction = Direction::Right,
|
||||||
|
b'"' => {
|
||||||
|
let n = self.shrink();
|
||||||
|
self.output.extend(n.to_string().as_bytes());
|
||||||
|
}
|
||||||
|
b'!' => {
|
||||||
|
let n = self.shrink();
|
||||||
|
self.output.push(n);
|
||||||
|
}
|
||||||
|
b'?' => {
|
||||||
|
let val = self
|
||||||
|
.input
|
||||||
|
.get(self.input_index)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default();
|
||||||
|
self.worm_in.push(val);
|
||||||
|
}
|
||||||
|
b'=' => {
|
||||||
|
let last_val = self.worm.last().map(|&p| self.get(p)).unwrap_or_default();
|
||||||
|
self.worm_in.push(last_val);
|
||||||
|
}
|
||||||
|
b'\\' => {
|
||||||
|
let val = self.shrink();
|
||||||
|
if val != 0 {
|
||||||
|
self.direction = match self.direction {
|
||||||
|
Direction::Up => Direction::Left,
|
||||||
|
Direction::Down => Direction::Right,
|
||||||
|
Direction::Left => Direction::Up,
|
||||||
|
Direction::Right => Direction::Down,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b'/' => {
|
||||||
|
let val = self.shrink();
|
||||||
|
if val != 0 {
|
||||||
|
self.direction = match self.direction {
|
||||||
|
Direction::Up => Direction::Right,
|
||||||
|
Direction::Down => Direction::Left,
|
||||||
|
Direction::Left => Direction::Down,
|
||||||
|
Direction::Right => Direction::Up,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b' ' | 0 => (),
|
||||||
|
other => panic!("{} not implemented", other as char),
|
||||||
|
}
|
||||||
|
if !dont_push_instruction {
|
||||||
|
self.worm_out.push(instruction);
|
||||||
|
}
|
||||||
|
self.move_to(front);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_to(&mut self, front: (usize, usize)) {
|
||||||
|
if let Some(input) = self.worm_in.pop() {
|
||||||
|
*self.get_mut(self.worm_head) = input;
|
||||||
|
self.worm.push(self.worm_head);
|
||||||
|
} else {
|
||||||
|
let mut next = self.worm_head;
|
||||||
|
for body_segment in self.worm.iter_mut().rev() {
|
||||||
|
self.program[next.1][next.0] = self.program[body_segment.1][body_segment.0];
|
||||||
|
(*body_segment, next) = (next, *body_segment);
|
||||||
|
}
|
||||||
|
*self.get_mut(next) = self.worm_out.pop().unwrap_or(b' ');
|
||||||
|
}
|
||||||
|
self.worm_head = front;
|
||||||
|
*self.get_mut(front) = b'@';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get the front number and move the body forward (leaves the head where it was).
|
||||||
|
/// also shits out any queued instruction
|
||||||
|
fn shrink(&mut self) -> u8 {
|
||||||
|
if let Some(neck) = self.worm.pop() {
|
||||||
|
let ret = self.get(neck);
|
||||||
|
let mut next = neck;
|
||||||
|
for body_segment in self.worm.iter_mut().rev() {
|
||||||
|
self.program[next.1][next.0] = self.program[body_segment.1][body_segment.0];
|
||||||
|
(*body_segment, next) = (next, *body_segment);
|
||||||
|
}
|
||||||
|
*self.get_mut(next) = self.worm_out.pop().unwrap_or(b' ');
|
||||||
|
ret
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, pos: (usize, usize)) -> u8 {
|
||||||
|
self.program[pos.1][pos.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mut(&mut self, pos: (usize, usize)) -> &mut u8 {
|
||||||
|
&mut self.program[pos.1][pos.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn front(&self) -> (usize, usize) {
|
||||||
|
let mut front = self.worm_head;
|
||||||
|
match self.direction {
|
||||||
|
Direction::Up => front.1 = front.1.wrapping_sub(1),
|
||||||
|
Direction::Down => front.1 = front.1.saturating_add(1),
|
||||||
|
Direction::Left => front.0 = front.0.wrapping_sub(1),
|
||||||
|
Direction::Right => front.0 = front.0.saturating_add(1),
|
||||||
|
}
|
||||||
|
front
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(source: &str) -> (Vec<Vec<u8>>, (usize, usize)) {
|
||||||
|
let mut program = Vec::new();
|
||||||
|
let mut width = 0;
|
||||||
|
let mut start_pos = (0, 0);
|
||||||
|
for (row, line) in source.lines().enumerate() {
|
||||||
|
width = width.max(line.len());
|
||||||
|
if let Some(col) = line.find('@') {
|
||||||
|
start_pos = (row, col);
|
||||||
|
}
|
||||||
|
program.push(line.as_bytes().to_vec());
|
||||||
|
}
|
||||||
|
for line in &mut program {
|
||||||
|
line.resize(width, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
(program, start_pos)
|
||||||
|
}
|
Loading…
Reference in a new issue