split crate into game and marble engine library
This commit is contained in:
parent
d5bb0f7ba0
commit
8b1eaaa630
20 changed files with 241 additions and 456 deletions
7
marble_engine/Cargo.toml
Normal file
7
marble_engine/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "marble_engine"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
191
marble_engine/src/board.rs
Normal file
191
marble_engine/src/board.rs
Normal 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
516
marble_engine/src/lib.rs
Normal 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
53
marble_engine/src/pos.rs
Normal 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
552
marble_engine/src/tile.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue