split crate into game and marble engine library

This commit is contained in:
Crispy 2025-03-27 18:05:59 +01:00
parent d5bb0f7ba0
commit 8b1eaaa630
20 changed files with 241 additions and 456 deletions

7
marble_engine/Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "marble_engine"
version = "0.2.1"
edition = "2021"
[dependencies]

191
marble_engine/src/board.rs Normal file
View file

@ -0,0 +1,191 @@
use super::{tile::*, Pos, PosInt};
#[derive(Debug, Clone, PartialEq)]
pub struct Board {
tiles: Vec<Tile>,
width: usize,
height: usize,
}
#[derive(Debug, Clone)]
pub struct ResizeDeltas {
pub x_pos: usize,
pub x_neg: usize,
pub y_pos: usize,
pub y_neg: usize,
}
impl Board {
pub fn parse(source: &str) -> Self {
let mut rows = Vec::new();
let mut width = 0;
let mut height = 0;
for line in source.lines() {
height += 1;
width = width.max(line.len());
let mut tiles = Vec::new();
for char in line.chars() {
tiles.push(Tile::from_char(char));
}
rows.push(tiles);
}
for line in &mut rows {
line.resize(width, Tile::BLANK);
}
let tiles = rows.into_iter().flatten().collect();
Self {
tiles,
width,
height,
}
}
pub fn serialize(&self) -> String {
let mut out = String::new();
for y in 0..self.height {
for x in 0..self.width {
let tile = self.get((x, y).into()).unwrap();
out.push(tile.to_char());
}
out.push('\n');
}
out
}
pub fn new_empty(width: usize, height: usize) -> Self {
let tiles = vec![Tile::BLANK; width * height];
Self {
tiles,
width,
height,
}
}
pub fn new_single(tile: Tile) -> Self {
Self {
tiles: vec![tile],
width: 1,
height: 1,
}
}
pub fn count_tiles(&self) -> usize {
let mut sum = 0;
for tile in &self.tiles {
if !matches!(tile, Tile::Open(OpenTile::Blank, _) | Tile::Block) {
sum += 1
}
}
sum
}
fn in_bounds(&self, p: Pos) -> bool {
p.x >= 0 && p.y >= 0 && p.x < self.width as PosInt && p.y < self.height as PosInt
}
fn get_unchecked(&self, p: Pos) -> Tile {
self.tiles[p.y as usize * self.width + p.x as usize]
}
pub fn get(&self, p: Pos) -> Option<Tile> {
if self.in_bounds(p) {
Some(self.get_unchecked(p))
} else {
None
}
}
pub fn get_or_blank(&self, p: Pos) -> Tile {
if self.in_bounds(p) {
self.get_unchecked(p)
} else {
Tile::BLANK
}
}
pub fn get_mut(&mut self, p: Pos) -> Option<&mut Tile> {
if self.in_bounds(p) {
Some(&mut self.tiles[p.y as usize * self.width + p.x as usize])
} else {
None
}
}
pub fn set(&mut self, p: Pos, tile: Tile) {
if self.in_bounds(p) {
self.tiles[p.y as usize * self.width + p.x as usize] = tile;
}
}
pub fn paste_board(&mut self, pos: Pos, source: &Board) {
for x in 0..source.width() {
for y in 0..source.height() {
let offset = (x, y).into();
if let Some(tile) = source.get(offset) {
self.set(offset + pos, tile);
}
}
}
}
pub fn get_rect(&self, pos: Pos, width: usize, height: usize) -> Board {
let mut out = Board::new_empty(width, height);
for x in 0..width {
for y in 0..height {
let offset = (x, y).into();
if let Some(tile) = self.get(offset + pos) {
out.set(offset, tile);
}
}
}
out
}
pub fn grow(&mut self, deltas: &ResizeDeltas) {
let new_width = self.width + deltas.x_neg + deltas.x_pos;
let new_height = self.height + deltas.y_neg + deltas.y_pos;
let mut new_board = Board::new_empty(new_width, new_height);
for x in 0..self.width {
for y in 0..self.height {
let tile = self.get_unchecked((x, y).into());
new_board.set((x + deltas.x_neg, y + deltas.y_neg).into(), tile);
}
}
*self = new_board;
}
pub fn shrink(&mut self, deltas: &ResizeDeltas) {
let new_width = self.width - deltas.x_neg - deltas.x_pos;
let new_height = self.height - deltas.y_neg - deltas.y_pos;
let mut new_board = Board::new_empty(new_width, new_height);
for x in 0..new_width {
for y in 0..new_height {
let tile = self.get_unchecked((x + deltas.x_neg, y + deltas.y_neg).into());
new_board.set((x, y).into(), tile);
}
}
*self = new_board;
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn get_marbles(&self) -> Vec<Pos> {
let mut out = Vec::new();
for y in 0..self.height {
for x in 0..self.width {
if let Tile::Marble { value: _, dir: _ } = self.get_unchecked((x, y).into()) {
out.push((x, y).into());
}
}
}
out
}
}

516
marble_engine/src/lib.rs Normal file
View file

@ -0,0 +1,516 @@
pub mod board;
pub mod pos;
pub mod tile;
use board::Board;
use pos::*;
use tile::*;
#[derive(Debug)]
pub struct Machine {
board: Board,
marbles: Vec<Pos>,
powered: Vec<Pos>,
input: Vec<u8>,
input_index: usize,
output: Vec<u8>,
steps: usize,
pub subtick_index: usize,
pub debug_subticks: Vec<DebugSubTick>,
}
#[derive(Debug)]
pub struct DebugSubTick {
pub board: Board,
pub pos: Option<Pos>,
}
impl Machine {
pub fn new_empty() -> Self {
Self {
board: Board::new_empty(5, 5),
marbles: Vec::new(),
powered: Vec::new(),
input: Vec::new(),
input_index: 0,
output: Vec::new(),
steps: 0,
subtick_index: 0,
debug_subticks: Vec::new(),
}
}
pub fn reset(&mut self) {
self.steps = 0;
self.input_index = 0;
self.output.clear();
self.powered.clear();
self.debug_subticks.clear();
self.subtick_index = 0;
}
pub fn set_board(&mut self, board: Board) {
self.marbles = board.get_marbles();
self.powered.clear();
self.board = board;
}
pub fn marbles(&self) -> &[Pos] {
&self.marbles
}
pub fn board(&self) -> &Board {
&self.board
}
pub fn output(&self) -> &[u8] {
&self.output
}
pub fn input(&self) -> &[u8] {
&self.input
}
pub fn input_index(&self) -> usize {
self.input_index
}
pub fn step_count(&self) -> usize {
self.steps
}
pub fn set_input(&mut self, bytes: Vec<u8>) {
self.input_index = self.input_index.min(bytes.len());
self.input = bytes;
}
pub fn step(&mut self) {
self.steps += 1;
#[cfg(debug_assertions)]
{
self.subtick_index = 0;
self.debug_subticks.clear();
self.debug_subticks.push(DebugSubTick {
board: self.board.clone(),
pos: None,
});
}
let old_marbles = self.marbles.len();
let mut new_marbles = Vec::new();
// activate all powered machines
for &pos in &self.powered {
match self.board.get_mut(pos) {
Some(Tile::Powerable(PTile::Comparator(_), board_power_state)) => {
// already handled at the power propagation stage (end of sim step)
*board_power_state = Power::OFF;
}
Some(Tile::Powerable(machine, board_power_state)) => {
let state = *board_power_state;
*board_power_state = Power::OFF;
let machine = *machine;
for dir in Direction::ALL {
if !state.get_dir(dir) {
continue;
}
let front_pos = dir.step(pos);
let Some(front_tile) = self.board.get_mut(front_pos) else {
continue;
};
// `machine` is being powered, in direction `dir`
match machine {
PTile::Math(op) => {
if front_tile.is_blank() {
let pos_a = dir.left().step(pos);
let pos_b = dir.right().step(pos);
let val_a = self.board.get_or_blank(pos_a).read_value();
let val_b = self.board.get_or_blank(pos_b).read_value();
let value = match op {
MathOp::Add => val_a.wrapping_add(val_b),
MathOp::Sub => val_a.wrapping_sub(val_b),
MathOp::Mul => val_a.wrapping_mul(val_b),
MathOp::Div => val_a.checked_div(val_b).unwrap_or_default(),
MathOp::Rem => val_a.checked_rem(val_b).unwrap_or_default(),
};
new_marbles.push((front_pos, value, dir));
}
}
PTile::IO => {
if front_tile == &Tile::BLANK && self.input_index < self.input.len()
{
let value = self.input[self.input_index] as MarbleValue;
self.input_index += 1;
new_marbles.push((front_pos, value, dir));
}
}
PTile::Silo => {
if front_tile == &Tile::BLANK {
new_marbles.push((front_pos, 0, dir));
}
}
PTile::Flipper => {
match front_tile {
Tile::Wire(wire_type, _) => {
*wire_type = match *wire_type {
WireType::Vertical => WireType::Horizontal,
WireType::Horizontal => WireType::Vertical,
WireType::Cross => WireType::Cross,
}
}
Tile::Mirror(mirror) => mirror.flip(),
Tile::Arrow(dir) => *dir = dir.opposite(),
_ => (),
};
}
PTile::Comparator(_) => unreachable!(),
}
}
}
Some(Tile::Button(state) | Tile::Wire(_, state)) => {
*state = false;
}
_ => unreachable!("non-powerable tile at {pos:?} in self.powered"),
};
}
self.powered.clear();
if self.marbles.is_empty() {
return;
}
#[derive(Clone, Copy, Debug)]
enum DirInfluence {
None,
One(Direction),
Multiple,
}
// #### find all direct bounces ####
let mut will_reverse_direction = vec![false; self.marbles.len()];
// todo store in tile to remove search through self.marbles
let mut influenced_direction = vec![DirInfluence::None; self.marbles.len()];
for (i, &pos) in self.marbles.iter().enumerate() {
let Some(Tile::Marble { value: _, dir }) = self.board.get(pos) else {
unreachable!()
};
let front_pos = dir.step(pos);
let Some(front_tile) = self.board.get(front_pos) else {
continue;
};
match front_tile {
Tile::Marble {
value: _,
dir: other_dir,
} => {
if other_dir != dir {
// this marble is facing another marble, and will therefore definitely bounce
will_reverse_direction[i] = true;
// the other marble will bounce too, either
let other_index =
self.marbles.iter().position(|m| *m == front_pos).unwrap();
let influence = &mut influenced_direction[other_index];
*influence = match *influence {
DirInfluence::None => DirInfluence::One(dir),
DirInfluence::One(_) => DirInfluence::Multiple,
DirInfluence::Multiple => DirInfluence::Multiple,
}
}
}
Tile::Arrow(arrow_dir) => {
if arrow_dir == dir.opposite() {
// bounce on a reverse facing arrow
will_reverse_direction[i] = true;
}
}
_ => (),
}
}
// #### apply all direct bounces ####
for (i, &pos) in self.marbles.iter().enumerate() {
let Some(Tile::Marble { value: _, dir }) = self.board.get_mut(pos) else {
unreachable!()
};
if will_reverse_direction[i] {
*dir = dir.opposite();
} else if let DirInfluence::One(new_dir) = influenced_direction[i] {
*dir = new_dir;
}
}
// #### new marbles ####
let mut claim_positions = Vec::new();
// prepare creating the new marbles
for &(pos, _val, _dir) in &new_marbles {
let Some(Tile::Open(OpenTile::Blank, claim)) = self.board.get_mut(pos) else {
unreachable!()
};
if claim.claim_indirect() {
claim_positions.push(pos);
}
}
// create new marbles
// new marbles are past old_marbles index, so will not move this step
for (pos, value, dir) in new_marbles {
let Some(Tile::Open(OpenTile::Blank, Claim::ClaimedIndirect)) = self.board.get_mut(pos)
else {
continue;
};
self.board.set(pos, Tile::Marble { value, dir });
self.marbles.push(pos);
}
// #### movement ####
// mark claims to figure out what spaces can be moved to
for &pos in &self.marbles[..old_marbles] {
let Some(Tile::Marble { value: _, dir }) = self.board.get(pos) else {
unreachable!()
};
let front_pos = dir.step(pos);
let Some(front_tile) = self.board.get_mut(front_pos) else {
continue;
};
if let Tile::Open(_type, claim) = front_tile {
if claim.claim() {
claim_positions.push(front_pos);
}
} else {
let target_pos = match front_tile {
Tile::Arrow(d) => d.step(front_pos),
Tile::Mirror(m) => m.new_dir(dir).step(front_pos),
Tile::Button(_) => dir.step(front_pos),
_ => continue,
};
let Some(target_tile) = self.board.get_mut(target_pos) else {
continue;
};
if let Tile::Open(_type, claim) = target_tile {
if claim.claim_indirect() {
claim_positions.push(front_pos);
}
}
}
}
let mut removed_marbles = Vec::new();
// move marbles
for (i, pos) in self.marbles[..old_marbles].iter_mut().enumerate() {
let Some(Tile::Marble { value, dir }) = self.board.get(*pos) else {
unreachable!()
};
let front_pos = dir.step(*pos);
let Some(front_tile) = self.board.get_mut(front_pos) else {
continue;
};
let mut move_to = |tile, target_pos, dir, board: &mut Board| {
let value = match tile {
OpenTile::Blank => value,
OpenTile::Digit(n) => value.wrapping_mul(10).wrapping_add(n as MarbleValue),
};
board.set(*pos, Tile::BLANK);
board.set(target_pos, Tile::Marble { value, dir });
*pos = target_pos;
};
if let Tile::Open(space_type, claim_state) = front_tile {
if *claim_state == Claim::Claimed {
move_to(*space_type, front_pos, dir, &mut self.board);
} else if *claim_state != Claim::Free {
// (Free means a marble was just here but moved earlier this tick)
// bounce on failed direct movement
self.board.set(
*pos,
Tile::Marble {
value,
dir: dir.opposite(),
},
);
}
} else {
let target_pos;
let mut is_button = false;
let mut new_dir = dir;
match front_tile {
Tile::Arrow(d) => {
target_pos = d.step(front_pos);
new_dir = *d;
}
Tile::Mirror(m) => {
new_dir = m.new_dir(dir);
target_pos = new_dir.step(front_pos);
}
Tile::Button(_) => {
is_button = true;
target_pos = dir.step(front_pos);
}
Tile::Powerable(PTile::Silo, _) => {
removed_marbles.push(i);
continue;
}
Tile::Powerable(PTile::IO, _) => {
removed_marbles.push(i);
self.output.push(value as u8);
continue;
}
_ => continue,
}
let Some(target_tile) = self.board.get_mut(target_pos) else {
continue;
};
if let Tile::Open(space_type, Claim::ClaimedIndirect) = target_tile {
move_to(*space_type, target_pos, new_dir, &mut self.board);
if is_button {
self.powered.push(front_pos);
}
}
}
}
for pos in claim_positions {
if let Some(Tile::Open(_, claim_state)) = self.board.get_mut(pos) {
*claim_state = Claim::Free;
}
}
// remove marbles
for &i in removed_marbles.iter().rev() {
self.board.set(self.marbles[i], Tile::BLANK);
self.marbles.swap_remove(i);
}
// propagate power
let mut i = 0;
while i < self.powered.len() {
let pos = self.powered[i];
let Some(tile) = self.board.get_mut(pos) else {
unreachable!()
};
match tile {
Tile::Button(state) => {
*state = true;
for dir in Direction::ALL {
let target_pos = dir.step(pos);
match self.board.get_mut(target_pos) {
Some(Tile::Powerable(_, state)) => {
if !state.get_dir(dir) {
state.add_dir(dir);
self.powered.push(target_pos);
}
}
Some(Tile::Wire(_, state)) => {
// only push it if it hasnt already been pushed
if !*state {
*state = true;
self.powered.push(target_pos);
}
}
_ => (),
}
}
#[cfg(debug_assertions)]
self.debug_subticks.push(DebugSubTick {
board: self.board.clone(),
pos: Some(pos),
});
}
Tile::Wire(wiretype, state) => {
*state = true;
for dir in wiretype.directions() {
let target_pos = dir.step(pos);
match self.board.get_mut(target_pos) {
Some(Tile::Powerable(_, state)) => {
if !state.get_dir(*dir) {
state.add_dir(*dir);
self.powered.push(target_pos);
}
}
Some(Tile::Wire(_, state)) => {
// only push it if it hasnt already been pushed
if !*state {
*state = true;
self.powered.push(target_pos);
}
}
_ => (),
}
}
#[cfg(debug_assertions)]
self.debug_subticks.push(DebugSubTick {
board: self.board.clone(),
pos: Some(pos),
});
}
Tile::Powerable(PTile::Comparator(comp), state) => {
let comp = *comp;
let state = *state;
for dir in Direction::ALL {
if !state.get_dir(dir) {
continue;
}
let front_pos = dir.step(pos);
let Some(front_tile) = self.board.get_mut(front_pos) else {
continue;
};
if matches!(front_tile, Tile::Wire(_, _) | Tile::Powerable(_, _)) {
let pos_a = dir.left().step(pos);
let pos_b = dir.right().step(pos);
let val_a = self.board.get_or_blank(pos_a).read_value();
let val_b = self.board.get_or_blank(pos_b).read_value();
let result = match comp {
Comparison::LessThan => val_a < val_b,
Comparison::GreaterThan => val_a > val_b,
Comparison::Equal => val_a == val_b,
Comparison::NotEqual => val_a != val_b,
};
if result {
match self.board.get_mut(front_pos) {
Some(Tile::Powerable(_, state)) => {
if !state.get_dir(dir) {
state.add_dir(dir);
self.powered.push(front_pos);
}
}
Some(Tile::Wire(_, state)) => {
if !*state {
*state = true;
self.powered.push(front_pos);
}
}
_ => (),
}
}
}
}
#[cfg(debug_assertions)]
self.debug_subticks.push(DebugSubTick {
board: self.board.clone(),
pos: Some(pos),
});
}
Tile::Powerable(_, _state) => {
#[cfg(debug_assertions)]
self.debug_subticks.push(DebugSubTick {
board: self.board.clone(),
pos: Some(pos),
});
}
_ => {
dbg!(tile);
unreachable!()
}
}
i += 1;
}
#[cfg(debug_assertions)]
{
self.debug_subticks.push(DebugSubTick {
board: self.board.clone(),
pos: None,
});
self.subtick_index = self.debug_subticks.len() - 1;
}
}
}

53
marble_engine/src/pos.rs Normal file
View file

@ -0,0 +1,53 @@
use std::ops::Add;
pub type PosInt = i16;
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Pos {
pub x: PosInt,
pub y: PosInt,
}
impl Pos {
pub fn min(self, other: Self) -> Self {
Self {
x: self.x.min(other.x),
y: self.y.min(other.y),
}
}
pub fn max(self, other: Self) -> Self {
Self {
x: self.x.max(other.x),
y: self.y.max(other.y),
}
}
}
impl From<(usize, usize)> for Pos {
fn from(value: (usize, usize)) -> Self {
Self {
x: value.0 as PosInt,
y: value.1 as PosInt,
}
}
}
impl From<(i32, i32)> for Pos {
fn from(value: (i32, i32)) -> Self {
Self {
x: value.0 as PosInt,
y: value.1 as PosInt,
}
}
}
impl Add for Pos {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}

552
marble_engine/src/tile.rs Normal file
View file

@ -0,0 +1,552 @@
use crate::Pos;
pub type MarbleValue = u32;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Tile {
Open(OpenTile, Claim),
Block,
Marble { value: MarbleValue, dir: Direction },
Mirror(MirrorType),
Arrow(Direction),
Button(bool),
Wire(WireType, bool),
Powerable(PTile, Power),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Claim {
Free,
ClaimedIndirect,
BlockedIndirect,
Claimed,
Blocked,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OpenTile {
Blank,
Digit(u8),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PTile {
Comparator(Comparison),
Math(MathOp),
Silo,
Flipper,
IO,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Power {
directions: u8,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MirrorType {
Forward,
Back,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MathOp {
Add,
Sub,
Mul,
Div,
Rem,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Comparison {
LessThan,
GreaterThan,
Equal,
NotEqual,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum WireType {
Vertical,
Horizontal,
Cross,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
impl Tile {
pub const BLANK: Self = Tile::Open(OpenTile::Blank, Claim::Free);
pub const fn from_char(c: char) -> Tile {
match c {
'o' => Tile::Marble {
value: 0,
dir: Direction::Down,
},
'*' => Tile::Button(false),
'-' => Tile::Wire(WireType::Horizontal, false),
'|' => Tile::Wire(WireType::Vertical, false),
'+' => Tile::Wire(WireType::Cross, false),
'/' => Tile::Mirror(MirrorType::Forward),
'\\' => Tile::Mirror(MirrorType::Back),
'^' => Tile::Arrow(Direction::Up),
'v' => Tile::Arrow(Direction::Down),
'<' => Tile::Arrow(Direction::Left),
'>' => Tile::Arrow(Direction::Right),
'=' => Tile::Powerable(PTile::Comparator(Comparison::Equal), Power::OFF),
'!' => Tile::Powerable(PTile::Comparator(Comparison::NotEqual), Power::OFF),
'L' => Tile::Powerable(PTile::Comparator(Comparison::LessThan), Power::OFF),
'G' => Tile::Powerable(PTile::Comparator(Comparison::GreaterThan), Power::OFF),
'I' | 'P' => Tile::Powerable(PTile::IO, Power::OFF),
'F' => Tile::Powerable(PTile::Flipper, Power::OFF),
'A' => Tile::Powerable(PTile::Math(MathOp::Add), Power::OFF),
'S' => Tile::Powerable(PTile::Math(MathOp::Sub), Power::OFF),
'M' => Tile::Powerable(PTile::Math(MathOp::Mul), Power::OFF),
'D' => Tile::Powerable(PTile::Math(MathOp::Div), Power::OFF),
'R' => Tile::Powerable(PTile::Math(MathOp::Rem), Power::OFF),
'B' => Tile::Powerable(PTile::Silo, Power::OFF),
d @ '0'..='9' => Tile::Open(OpenTile::Digit(d as u8 - b'0'), Claim::Free),
'#' => Tile::Block,
_ => Tile::Open(OpenTile::Blank, Claim::Free),
}
}
pub fn to_char(self) -> char {
match self {
Tile::Open(OpenTile::Blank, _) => ' ',
Tile::Block => '#',
Tile::Marble { value: _, dir: _ } => 'o',
Tile::Open(OpenTile::Digit(n), _) => (b'0' + n) as char,
Tile::Mirror(dir) => match dir {
MirrorType::Forward => '/',
MirrorType::Back => '\\',
},
Tile::Arrow(dir) => match dir {
Direction::Up => '^',
Direction::Down => 'v',
Direction::Left => '<',
Direction::Right => '>',
},
Tile::Button(_) => '*',
Tile::Wire(wire, _) => match wire {
WireType::Vertical => '|',
WireType::Horizontal => '-',
WireType::Cross => '+',
},
Tile::Powerable(tile, _) => match tile {
PTile::Comparator(comp) => match comp {
Comparison::LessThan => 'L',
Comparison::GreaterThan => 'G',
Comparison::Equal => '=',
Comparison::NotEqual => '!',
},
PTile::Math(math) => match math {
MathOp::Add => 'A',
MathOp::Sub => 'S',
MathOp::Mul => 'M',
MathOp::Div => 'D',
MathOp::Rem => 'R',
},
PTile::Silo => 'B',
PTile::Flipper => 'F',
PTile::IO => 'I',
},
}
}
pub fn is_blank(self) -> bool {
matches!(self, Tile::Open(OpenTile::Blank, _))
}
pub fn read_value(self) -> MarbleValue {
match self {
Tile::Marble { value, dir: _ } => value,
Tile::Open(OpenTile::Digit(d), _) => d as MarbleValue,
_ => 0,
}
}
pub fn texture(self) -> &'static str {
match self {
Tile::Open(OpenTile::Blank, _) => "",
Tile::Block => "block",
Tile::Marble { value: _, dir: _ } => "marble",
Tile::Open(OpenTile::Digit(n), _) => match n {
0 => "tile_digit_0",
1 => "tile_digit_1",
2 => "tile_digit_2",
3 => "tile_digit_3",
4 => "tile_digit_4",
5 => "tile_digit_5",
6 => "tile_digit_6",
7 => "tile_digit_7",
8 => "tile_digit_8",
9 => "tile_digit_9",
_ => unreachable!(),
},
Tile::Mirror(mirror) => mirror.texture_name(),
Tile::Arrow(dir) => dir.arrow_tile_texture_name(),
Tile::Button(state) => {
if state {
return "button_on";
}
"button_off"
}
Tile::Wire(wire, state) => {
if state {
return wire.texture_name_on();
}
wire.texture_name_off()
}
Tile::Powerable(tile, state) => {
if state.any() {
return match tile {
PTile::Comparator(comp) => comp.texture_name_on(),
PTile::Math(math_op) => math_op.texture_name_on(),
PTile::Silo => "silo_on",
PTile::Flipper => "flipper_on",
PTile::IO => "io_tile_on",
};
}
match tile {
PTile::Comparator(comp) => comp.texture_name_off(),
PTile::Math(math_op) => math_op.texture_name_off(),
PTile::Silo => "silo_off",
PTile::Flipper => "flipper_off",
PTile::IO => "io_tile_off",
}
}
}
}
}
impl Direction {
pub const ALL: [Direction; 4] = [
Direction::Up,
Direction::Down,
Direction::Left,
Direction::Right,
];
pub fn opposite(self) -> Direction {
match self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}
pub fn right(self) -> Direction {
match self {
Direction::Up => Direction::Right,
Direction::Down => Direction::Left,
Direction::Left => Direction::Up,
Direction::Right => Direction::Down,
}
}
pub fn left(self) -> Direction {
self.right().opposite()
}
pub fn step(self, mut pos: Pos) -> Pos {
match self {
Direction::Up => pos.y -= 1,
Direction::Down => pos.y += 1,
Direction::Left => pos.x -= 1,
Direction::Right => pos.x += 1,
}
pos
}
pub const fn arrow_tile_texture_name(self) -> &'static str {
match self {
Direction::Up => "arrow_up",
Direction::Down => "arrow_down",
Direction::Left => "arrow_left",
Direction::Right => "arrow_right",
}
}
pub const fn debug_arrow_texture_name(self) -> &'static str {
match self {
Direction::Up => "debug_arrow_up",
Direction::Down => "debug_arrow_down",
Direction::Left => "debug_arrow_left",
Direction::Right => "debug_arrow_right",
}
}
pub const fn arrow_tile_human_name(self) -> &'static str {
match self {
Direction::Up => "Up Arrow",
Direction::Down => "Down Arrow",
Direction::Left => "Left Arrow",
Direction::Right => "Right Arrow",
}
}
pub const fn arrow_texture_name(self) -> &'static str {
match self {
Direction::Up => "direction_up",
Direction::Down => "direction_down",
Direction::Left => "direction_left",
Direction::Right => "direction_right",
}
}
}
impl WireType {
pub fn has_output(self, dir: Direction) -> bool {
match self {
WireType::Vertical => matches!(dir, Direction::Up | Direction::Down),
WireType::Horizontal => matches!(dir, Direction::Right | Direction::Left),
WireType::Cross => true,
}
}
pub fn directions(self) -> &'static [Direction] {
match self {
WireType::Vertical => &[Direction::Up, Direction::Down],
WireType::Horizontal => &[Direction::Left, Direction::Right],
WireType::Cross => &Direction::ALL,
}
}
pub fn next(&mut self) {
*self = match self {
WireType::Vertical => WireType::Horizontal,
WireType::Horizontal => WireType::Cross,
WireType::Cross => WireType::Vertical,
}
}
pub fn prev(&mut self) {
*self = match self {
WireType::Vertical => WireType::Cross,
WireType::Horizontal => WireType::Vertical,
WireType::Cross => WireType::Horizontal,
}
}
pub const fn texture_name_on(self) -> &'static str {
match self {
WireType::Vertical => "wire_vertical_on",
WireType::Horizontal => "wire_horizontal_on",
WireType::Cross => "wire_cross_on",
}
}
pub const fn texture_name_off(self) -> &'static str {
match self {
WireType::Vertical => "wire_vertical_off",
WireType::Horizontal => "wire_horizontal_off",
WireType::Cross => "wire_cross_off",
}
}
pub const fn human_name(self) -> &'static str {
match self {
WireType::Vertical => "Vertical Wire",
WireType::Horizontal => "Horizontal Wire",
WireType::Cross => "Wire Cross",
}
}
}
impl MirrorType {
pub fn new_dir(self, dir: Direction) -> Direction {
match self {
MirrorType::Forward => match dir {
Direction::Up => Direction::Right,
Direction::Down => Direction::Left,
Direction::Left => Direction::Down,
Direction::Right => Direction::Up,
},
MirrorType::Back => match dir {
Direction::Up => Direction::Left,
Direction::Down => Direction::Right,
Direction::Left => Direction::Up,
Direction::Right => Direction::Down,
},
}
}
pub fn flip(&mut self) {
*self = match self {
MirrorType::Forward => MirrorType::Back,
MirrorType::Back => MirrorType::Forward,
}
}
pub const fn texture_name(self) -> &'static str {
match self {
MirrorType::Forward => "mirror_forward",
MirrorType::Back => "mirror_back",
}
}
pub const fn human_name(self) -> &'static str {
match self {
MirrorType::Forward => "Mirror A",
MirrorType::Back => "Mirror B",
}
}
}
impl MathOp {
pub const fn human_name(self) -> &'static str {
match self {
MathOp::Add => "Math: Add",
MathOp::Sub => "Math: Subtract",
MathOp::Mul => "Math: Multiply",
MathOp::Div => "Math: Divide",
MathOp::Rem => "Math: Remainder",
}
}
pub const fn texture_name_on(self) -> &'static str {
match self {
MathOp::Add => "add_on",
MathOp::Sub => "sub_on",
MathOp::Mul => "mul_on",
MathOp::Div => "div_on",
MathOp::Rem => "rem_on",
}
}
pub const fn texture_name_off(self) -> &'static str {
match self {
MathOp::Add => "add_off",
MathOp::Sub => "sub_off",
MathOp::Mul => "mul_off",
MathOp::Div => "div_off",
MathOp::Rem => "rem_off",
}
}
pub fn next(&mut self) {
*self = match self {
MathOp::Add => MathOp::Sub,
MathOp::Sub => MathOp::Mul,
MathOp::Mul => MathOp::Div,
MathOp::Div => MathOp::Rem,
MathOp::Rem => MathOp::Add,
}
}
pub fn prev(&mut self) {
*self = match self {
MathOp::Add => MathOp::Rem,
MathOp::Sub => MathOp::Add,
MathOp::Mul => MathOp::Sub,
MathOp::Div => MathOp::Mul,
MathOp::Rem => MathOp::Div,
}
}
}
impl Comparison {
pub const fn human_name(self) -> &'static str {
match self {
Comparison::LessThan => "Comparator: Less than",
Comparison::GreaterThan => "Comparator: Greater than",
Comparison::Equal => "Comparator: Equal",
Comparison::NotEqual => "Comparator: Not Equal",
}
}
pub const fn texture_name_on(self) -> &'static str {
match self {
Comparison::LessThan => "lt_on",
Comparison::GreaterThan => "gt_on",
Comparison::Equal => "eq_on",
Comparison::NotEqual => "neq_on",
}
}
pub const fn texture_name_off(self) -> &'static str {
match self {
Comparison::LessThan => "lt_off",
Comparison::GreaterThan => "gt_off",
Comparison::Equal => "eq_off",
Comparison::NotEqual => "neq_off",
}
}
pub fn next(&mut self) {
*self = match self {
Comparison::LessThan => Comparison::GreaterThan,
Comparison::GreaterThan => Comparison::Equal,
Comparison::Equal => Comparison::NotEqual,
Comparison::NotEqual => Comparison::LessThan,
}
}
pub fn prev(&mut self) {
*self = match self {
Comparison::LessThan => Comparison::NotEqual,
Comparison::GreaterThan => Comparison::LessThan,
Comparison::Equal => Comparison::GreaterThan,
Comparison::NotEqual => Comparison::Equal,
}
}
}
impl Claim {
#[must_use]
/// returns `was_free`
pub fn claim(&mut self) -> bool {
let mut was_free = false;
*self = match self {
Claim::Free => {
was_free = true;
Claim::Claimed
}
Claim::ClaimedIndirect | Claim::BlockedIndirect => Claim::Claimed,
Claim::Claimed | Claim::Blocked => Claim::Blocked,
};
was_free
}
#[must_use]
/// returns `was_free`
pub fn claim_indirect(&mut self) -> bool {
let mut was_free = false;
*self = match self {
Claim::Free => {
was_free = true;
Claim::ClaimedIndirect
}
Claim::ClaimedIndirect => Claim::BlockedIndirect,
_ => *self,
};
was_free
}
}
impl Power {
pub const OFF: Self = Self { directions: 0 };
#[inline]
pub fn any(self) -> bool {
self.directions != 0
}
#[inline]
pub fn get_dir(self, dir: Direction) -> bool {
self.directions & (1 << (dir as u8)) != 0
}
#[inline]
pub fn add_dir(&mut self, dir: Direction) {
self.directions |= 1 << (dir as u8)
}
}