Refactor into different crates

This commit is contained in:
hal8174 2025-01-18 17:30:55 +01:00
parent 94473c64e0
commit dfdeae5638
82 changed files with 624 additions and 647 deletions

16
factorio-core/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "factorio-core"
version = "0.1.0"
edition = "2024"
[dev-dependencies]
criterion = "0.3"
[dependencies]
image = "0.25.5"
proptest = "1.5.0"
proptest-derive = "0.5.0"
rand = "0.8.5"
serde = { version = "1.0.217", features = ["derive"] }
termcolor = "1.4.1"

75
factorio-core/src/aabb.rs Normal file
View file

@ -0,0 +1,75 @@
use crate::prelude::*;
/// Axis Aligned Bounding Box
/// The max position is inclusive
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AABB {
max: Position,
min: Position,
}
impl AABB {
pub fn new(min: Position, max: Position) -> Self {
Self { min, max }
}
pub fn combine(self, other: Self) -> Self {
AABB::new(
Position::new(
i32::min(self.min.x, other.min.x),
i32::min(self.min.y, other.min.y),
),
Position::new(
i32::max(self.max.x, other.max.x),
i32::max(self.max.y, other.max.y),
),
)
}
pub fn min(&self) -> Position {
self.min
}
pub fn max(&self) -> Position {
self.max
}
pub fn size(&self) -> Position {
(self.max - self.min) + Position::new(1, 1)
}
// Returns true in case the aabb overlap
pub fn collision(self, other: Self) -> bool {
self.min.x <= other.max.x
&& self.max.x >= other.min.x
&& self.min.y <= other.max.y
&& self.max.y >= other.min.y
}
}
#[cfg(test)]
mod test {
use crate::prelude::*;
#[test]
fn collision() {
let a = AABB::new(Position::new(0, 0), Position::new(8, 8));
let b = AABB::new(Position::new(8, 8), Position::new(8, 8));
assert!(AABB::collision(a, b));
let b = AABB::new(Position::new(9, 8), Position::new(9, 8));
assert!(!AABB::collision(a, b));
}
#[test]
fn combine() {
let a = AABB::new(Position::new(9, 0), Position::new(9, 0));
let b = AABB::new(Position::new(0, 9), Position::new(0, 9));
assert_eq!(
AABB::combine(a, b),
AABB::new(Position::new(0, 0), Position::new(9, 9))
);
}
}

View file

@ -0,0 +1,94 @@
use crate::prelude::*;
/// The size field is in block space
#[derive(Clone, Copy, Debug)]
pub struct Block {
pos: Position,
dir: Direction,
size: Position,
}
impl Block {
pub fn new(pos: Position, dir: Direction, size: Position) -> Self {
Self { pos, dir, size }
}
pub fn pos(&self) -> Position {
self.pos
}
pub fn dir(&self) -> Direction {
self.dir
}
pub fn size(&self) -> Position {
self.size
}
pub fn get_aabb(&self) -> AABB {
let npos = match self.dir {
Direction::Up => self.pos,
Direction::Right => self.pos.in_direction(&Direction::Left, self.size.y - 1),
Direction::Down => self.pos - (self.size - Position::new(1, 1)),
Direction::Left => self.pos.in_direction(&Direction::Up, self.size.x - 1),
};
let nsize = match self.dir {
Direction::Up | Direction::Down => self.size,
Direction::Right | Direction::Left => Position {
x: self.size.y,
y: self.size.x,
},
};
AABB::new(npos, npos + nsize - Position::new(1, 1))
}
pub fn world_to_block(&self) -> Transformation {
self.block_to_world().invert()
}
pub fn block_to_world(&self) -> Transformation {
Transformation::new(self.dir, self.pos)
}
}
impl Transformable for Block {
fn transform(&self, t: Transformation) -> Self {
Self::new(self.pos.transform(t), self.dir.transform(t), self.size)
}
}
#[cfg(test)]
mod test {
use super::*;
use proptest::{prop_assert_eq, proptest};
#[test]
fn transformations() {
let b = Block::new(Position::new(10, 5), Direction::Right, Position::new(4, 3));
let t = b.block_to_world();
dbg!(t);
let p = Position::new(3, 2);
let m = p.transform(t);
assert_eq!(m, Position::new(8, 8));
}
proptest! {
#[test]
fn identity_transformation(dir: Direction, pos: Position, size: Position) {
let b = Block::new(pos, dir, size);
let t = b.world_to_block();
let n = b.transform(t);
prop_assert_eq!(n.dir, Direction::Up);
prop_assert_eq!(n.pos, Position::new(0, 0));
prop_assert_eq!(n.size, size);
}
}
}

View file

@ -0,0 +1,21 @@
use std::ops::Index;
use termcolor::Color;
pub static COLORS: Cyclic<Color, 6> = Cyclic([
Color::Red,
Color::Green,
Color::Yellow,
Color::Blue,
Color::Magenta,
Color::Cyan,
]);
pub struct Cyclic<T, const N: usize>([T; N]);
impl<T, const N: usize> Index<usize> for Cyclic<T, N> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index % N]
}
}

View file

@ -0,0 +1,100 @@
use crate::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Deserialize, Serialize)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub enum Direction {
Up,
Right,
Down,
Left,
}
impl Direction {
pub fn from_neighbors(pos: &Position, neighbor: &Position) -> Self {
let x_diff = pos.x as i64 - neighbor.x as i64;
let y_diff = pos.y as i64 - neighbor.y as i64;
match (x_diff, y_diff) {
(1, 0) => Direction::Left,
(0, 1) => Direction::Up,
(-1, 0) => Direction::Right,
(0, -1) => Direction::Down,
_ => {
panic!("Positions are not neighbors.")
}
}
}
pub fn vertical(&self) -> bool {
match self {
Direction::Up => true,
Direction::Right => false,
Direction::Down => true,
Direction::Left => false,
}
}
pub fn horizontal(&self) -> bool {
!self.vertical()
}
pub fn reverse(&self) -> Self {
match self {
Direction::Up => Direction::Down,
Direction::Right => Direction::Left,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
}
}
pub fn clockwise(&self) -> Self {
match self {
Direction::Up => Direction::Right,
Direction::Right => Direction::Down,
Direction::Down => Direction::Left,
Direction::Left => Direction::Up,
}
}
pub fn counter_clockwise(&self) -> Self {
match self {
Direction::Up => Direction::Left,
Direction::Right => Direction::Up,
Direction::Down => Direction::Right,
Direction::Left => Direction::Down,
}
}
pub fn get_index(&self) -> u8 {
match self {
Direction::Up => 0,
Direction::Right => 1,
Direction::Down => 2,
Direction::Left => 3,
}
}
pub fn from_index(i: u8) -> Self {
match i {
0 => Direction::Up,
1 => Direction::Right,
2 => Direction::Down,
3 => Direction::Left,
_ => panic!(),
}
}
}
impl rand::prelude::Distribution<Direction> for rand::distributions::Standard {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> Direction {
let a = [
Direction::Up,
Direction::Right,
Direction::Down,
Direction::Left,
];
let r = rng.gen_range(0..4);
a[r]
}
}

18
factorio-core/src/lib.rs Normal file
View file

@ -0,0 +1,18 @@
pub mod aabb;
pub mod block;
pub mod color;
pub mod direction;
pub mod pathfield;
pub mod position;
pub mod transformation;
pub mod visualize;
pub mod prelude {
pub use crate::{
aabb::AABB,
block::Block,
direction::Direction,
position::{Position, PositionType},
transformation::{Transformable, Transformation},
};
}

View file

@ -0,0 +1,148 @@
use std::io::{self, Write};
use termcolor::{ColorSpec, StandardStream, WriteColor};
use crate::prelude::*;
#[derive(Clone, Debug, Copy)]
pub enum PathField {
Belt {
pos: Position,
dir: Direction,
},
Underground {
pos: Position,
dir: Direction,
len: u8,
},
}
impl PathField {
pub fn pos(&self) -> &Position {
match self {
PathField::Belt { pos, dir: _ } => pos,
PathField::Underground {
pos,
dir: _,
len: _,
} => pos,
}
}
pub fn dir(&self) -> &Direction {
match self {
PathField::Belt { pos: _, dir } => dir,
PathField::Underground {
pos: _,
dir,
len: _,
} => dir,
}
}
pub fn end_pos(&self) -> (Position, Direction) {
match self {
PathField::Belt { pos, dir } => (*pos, *dir),
PathField::Underground { pos, dir, len } => {
(pos.in_direction(dir, *len as PositionType), *dir)
}
}
}
pub fn offset(&self, offset: &Position) -> Self {
match self {
PathField::Belt { pos, dir } => PathField::Belt {
pos: Position::new(pos.x + offset.x, pos.y + offset.y),
dir: *dir,
},
PathField::Underground { pos, dir, len } => PathField::Underground {
pos: Position::new(pos.x + offset.x, pos.y + offset.y),
dir: *dir,
len: *len,
},
}
}
pub fn cost(&self) -> usize {
match self {
PathField::Belt { pos: _, dir: _ } => 150,
PathField::Underground {
pos: _,
dir: _,
len: _,
} => 1750,
}
}
}
pub fn print_map<F>(width: i32, height: i32, f: F) -> io::Result<()>
where
F: Fn(i32, i32) -> (ColorSpec, &'static str),
{
let stdout = &mut StandardStream::stdout(termcolor::ColorChoice::Always);
let width_digits = (width - 1).ilog10() + 1;
let height_digits = (height - 1).ilog10() + 1;
// print header
for i in 0..width_digits {
let d = width_digits - i - 1;
//padding
for _ in 0..height_digits {
write!(stdout, " ")?;
}
for x in 0..width {
let digits = x / (i32::pow(10, d));
if digits == 0 && d > 0 {
write!(stdout, " ")?;
} else {
write!(
stdout,
"{}",
char::from_u32((digits % 10) as u32 + 48).unwrap()
)?;
}
}
writeln!(stdout)?;
}
for y in 0..height {
write!(stdout, "{:1$}", y, height_digits as usize)?;
for x in 0..width {
let (c, s) = f(x, y);
stdout.set_color(&c)?;
write!(stdout, "{:1}", s)?;
stdout.reset()?;
}
writeln!(stdout, "{:1$}", y, height_digits as usize)?;
}
for i in 0..width_digits {
let d = width_digits - i - 1;
//padding
for _ in 0..height_digits {
write!(stdout, " ")?;
}
for x in 0..width {
let digits = x / (i32::pow(10, d));
if digits == 0 && d > 0 {
write!(stdout, " ")?;
} else {
write!(
stdout,
"{}",
char::from_u32((digits % 10) as u32 + 48).unwrap()
)?;
}
}
writeln!(stdout)?;
}
Ok(())
}

View file

@ -0,0 +1,74 @@
use crate::prelude::*;
use serde::{Deserialize, Serialize};
pub type PositionType = i32;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct Position {
pub x: PositionType,
pub y: PositionType,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Dimension {
pub width: usize,
pub height: usize,
}
impl Position {
pub fn new(x: PositionType, y: PositionType) -> Self {
Self { x, y }
}
pub fn in_direction(&self, dir: &Direction, len: PositionType) -> Position {
match dir {
Direction::Up => Position::new(self.x, self.y - len),
Direction::Right => Position::new(self.x + len, self.y),
Direction::Down => Position::new(self.x, self.y + len),
Direction::Left => Position::new(self.x - len, self.y),
}
}
pub fn in_range(&self, min: &Position, max: &Position) -> Option<&Position> {
if self.x < min.x {
return None;
}
if self.x >= max.x {
return None;
}
if self.y < min.y {
return None;
}
if self.y >= max.y {
return None;
}
Some(self)
}
}
impl std::ops::Add for Position {
type Output = Position;
fn add(mut self, rhs: Self) -> Self::Output {
self.x += rhs.x;
self.y += rhs.y;
self
}
}
impl std::ops::Sub for Position {
type Output = Position;
fn sub(mut self, rhs: Self) -> Self::Output {
self.x -= rhs.x;
self.y -= rhs.y;
self
}
}
impl From<(i32, i32)> for Position {
fn from(value: (i32, i32)) -> Self {
Position::new(value.0, value.1)
}
}

View file

@ -0,0 +1,111 @@
use crate::prelude::*;
pub trait Transformable {
fn transform(&self, t: Transformation) -> Self;
}
#[derive(Clone, Copy, Debug)]
pub struct Transformation {
rot: Direction,
pos: Position,
}
impl Transformation {
pub fn new(rot: Direction, pos: Position) -> Self {
Self { rot, pos }
}
pub fn rot(&self) -> Direction {
self.rot
}
pub fn invert(self) -> Self {
Self::new(
match self.rot {
Direction::Up => Direction::Up,
Direction::Right => Direction::Left,
Direction::Down => Direction::Down,
Direction::Left => Direction::Right,
},
match self.rot {
Direction::Up => Position::new(-self.pos.x, -self.pos.y),
Direction::Right => Position::new(-self.pos.y, self.pos.x),
Direction::Down => self.pos,
Direction::Left => Position::new(self.pos.y, -self.pos.x),
},
)
}
pub fn chain(self, other: Self) -> Self {
Self::new(self.rot.transform(other), self.pos.transform(other))
}
}
impl Transformable for Position {
fn transform(&self, t: Transformation) -> Self {
(match t.rot {
Direction::Up => *self,
Direction::Right => Position::new(-self.y, self.x),
Direction::Down => Position::new(-self.x, -self.y),
Direction::Left => Position::new(self.y, -self.x),
}) + t.pos
}
}
impl Transformable for Direction {
fn transform(&self, t: Transformation) -> Self {
match (t.rot, self) {
(Direction::Up, _) => *self,
(Direction::Right, Direction::Up) => Direction::Right,
(Direction::Right, Direction::Right) => Direction::Down,
(Direction::Right, Direction::Down) => Direction::Left,
(Direction::Right, Direction::Left) => Direction::Up,
(Direction::Down, Direction::Up) => Direction::Down,
(Direction::Down, Direction::Right) => Direction::Left,
(Direction::Down, Direction::Down) => Direction::Up,
(Direction::Down, Direction::Left) => Direction::Right,
(Direction::Left, Direction::Up) => Direction::Left,
(Direction::Left, Direction::Right) => Direction::Up,
(Direction::Left, Direction::Down) => Direction::Right,
(Direction::Left, Direction::Left) => Direction::Down,
}
}
}
#[cfg(test)]
mod test {
use crate::prelude::*;
use proptest::{prop_assert_eq, proptest};
#[test]
fn position() {
let p = Position::new(3, 5);
let t = Transformation::new(Direction::Up, Position::new(-3, -5));
assert_eq!(p.transform(t), Position::new(0, 0));
let t = Transformation::new(Direction::Down, Position::new(-3, -5));
assert_eq!(p.transform(t), Position::new(-6, -10));
}
proptest! {
#[test]
fn invert(dir: Direction, pos: Position) {
let o = Transformation::new(dir, pos);
let i = o.invert();
let c = o.chain(i);
prop_assert_eq!(c.rot, Direction::Up, "o: {:?}, i: {:?}, c: {:?}", o, i, c);
prop_assert_eq!(
c.pos,
Position::new(0, 0),
"o: {:?}, i: {:?}, c: {:?}",
o,
i,
c
);
}
}
}

View file

@ -0,0 +1,432 @@
use super::Visualization;
use crate::{prelude::Direction, visualize::Symbol};
use image::{GenericImage, Rgba, RgbaImage};
pub(super) fn draw<I: GenericImage<Pixel = Rgba<u8>>>(v: &Visualization, image: &mut I) {
assert_eq!(
(v.size.x as u32 * 10, v.size.y as u32 * 10),
image.dimensions()
);
for (pos, (symbol, fg, bg)) in &v.symbols {
match symbol {
Symbol::Arrow(dir) => {
if let Some(c) = bg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for x in 0..10 {
for y in 0..10 {
image.put_pixel((pos.x * 10 + x) as u32, (pos.y * 10 + y) as u32, rgba);
}
}
}
if let Some(c) = fg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for p in rotate_iter(*dir, arrow()) {
image.put_pixel((pos.x * 10) as u32 + p.0, (pos.y * 10) as u32 + p.1, rgba);
}
}
}
Symbol::ArrowEnter(dir) => {
if let Some(c) = bg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for x in 0..10 {
for y in 0..10 {
image.put_pixel((pos.x * 10 + x) as u32, (pos.y * 10 + y) as u32, rgba);
}
}
}
if let Some(c) = fg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for p in rotate_iter(*dir, arrow_enter()) {
image.put_pixel((pos.x * 10) as u32 + p.0, (pos.y * 10) as u32 + p.1, rgba);
}
}
}
Symbol::ArrowExit(dir) => {
if let Some(c) = bg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for x in 0..10 {
for y in 0..10 {
image.put_pixel((pos.x * 10 + x) as u32, (pos.y * 10 + y) as u32, rgba);
}
}
}
if let Some(c) = fg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for p in rotate_iter(*dir, arrow_exit()) {
image.put_pixel((pos.x * 10) as u32 + p.0, (pos.y * 10) as u32 + p.1, rgba);
}
}
}
Symbol::Char(ch) => {
if let Some(c) = bg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for x in 0..10 {
for y in 0..10 {
image.put_pixel((pos.x * 10 + x) as u32, (pos.y * 10 + y) as u32, rgba);
}
}
}
if let Some(c) = fg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for p in char(*ch) {
image.put_pixel((pos.x * 10) as u32 + p.0, (pos.y * 10) as u32 + p.1, rgba);
}
}
}
Symbol::Block => {
if let Some(c) = fg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for x in 1..9 {
for y in 1..9 {
image.put_pixel((pos.x * 10 + x) as u32, (pos.y * 10 + y) as u32, rgba);
}
}
}
if let Some(c) = bg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for x in 0..10 {
image.put_pixel((pos.x * 10 + x) as u32, (pos.y * 10) as u32, rgba);
image.put_pixel((pos.x * 10 + x) as u32, (pos.y * 10 + 9) as u32, rgba);
}
for y in 1..9 {
image.put_pixel((pos.x * 10) as u32, (pos.y * 10 + y) as u32, rgba);
image.put_pixel((pos.x * 10 + 9) as u32, (pos.y * 10 + y) as u32, rgba);
}
}
}
Symbol::Space => {
if let Some(c) = bg.as_ref() {
let rgba = Rgba::from([c.r, c.g, c.b, 255]);
for x in 0..10 {
for y in 0..10 {
image.put_pixel((pos.x * 10 + x) as u32, (pos.y * 10 + y) as u32, rgba);
}
}
}
}
}
}
}
pub fn image_grid(
visualizations: &[Visualization],
width: u32,
height: u32,
columns: u32,
) -> RgbaImage {
let border_color = Rgba::from([255, 255, 255, 255]);
let rows = visualizations.len().div_ceil(columns as usize) as u32;
let mut img = RgbaImage::new((width * 10 + 2) * columns, (height * 10 + 2) * rows);
for (i, v) in visualizations.iter().enumerate() {
let i = i as u32;
let r = i / columns;
let c = i % columns;
let size = v.size();
let mut sub_img = img.sub_image(
1 + c * (width * 10 + 2),
1 + r * (height * 10 + 2),
(size.x as u32) * 10,
(size.y as u32) * 10,
);
draw(v, &mut *sub_img);
for x in 0..(size.x as u32 * 10 + 2) {
img.put_pixel(
c * (width * 10 + 2) + x,
r * (height * 10 + 2),
border_color,
);
img.put_pixel(
c * (width * 10 + 2) + x,
r * (height * 10 + 2) + (size.y as u32) * 10 + 1,
border_color,
);
}
for y in 0..(size.y as u32 * 10 + 2) {
img.put_pixel(
c * (width * 10 + 2),
r * (height * 10 + 2) + y,
border_color,
);
img.put_pixel(
c * (width * 10 + 2) + (size.x as u32) * 10 + 1,
r * (height * 10 + 2) + y,
border_color,
);
}
}
img
}
fn rotate(dir: Direction, pos: (u32, u32)) -> (u32, u32) {
match dir {
Direction::Up => pos,
Direction::Right => (9 - pos.1, pos.0),
Direction::Down => (9 - pos.0, 9 - pos.1),
Direction::Left => (pos.1, 9 - pos.0),
}
}
fn rotate_iter(
dir: Direction,
iter: impl Iterator<Item = (u32, u32)>,
) -> impl Iterator<Item = (u32, u32)> {
iter.map(move |p| rotate(dir, p))
}
fn arrow() -> impl Iterator<Item = (u32, u32)> {
[
(4, 1),
(5, 1),
(3, 2),
(4, 2),
(5, 2),
(6, 2),
(2, 3),
(3, 3),
(4, 3),
(5, 3),
(6, 3),
(7, 3),
(1, 4),
(2, 4),
(4, 4),
(5, 4),
(7, 4),
(8, 4),
(4, 5),
(5, 5),
(4, 6),
(5, 6),
(4, 7),
(5, 7),
(4, 8),
(5, 8),
]
.into_iter()
}
fn arrow_enter() -> impl Iterator<Item = (u32, u32)> {
[
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 1),
(7, 1),
(3, 2),
(4, 2),
(5, 2),
(6, 2),
(2, 3),
(3, 3),
(4, 3),
(5, 3),
(6, 3),
(7, 3),
(1, 4),
(2, 4),
(4, 4),
(5, 4),
(7, 4),
(8, 4),
(4, 5),
(5, 5),
(4, 6),
(5, 6),
(4, 7),
(5, 7),
(4, 8),
(5, 8),
]
.into_iter()
}
fn arrow_exit() -> impl Iterator<Item = (u32, u32)> {
[
(4, 1),
(5, 1),
(3, 2),
(4, 2),
(5, 2),
(6, 2),
(2, 3),
(3, 3),
(4, 3),
(5, 3),
(6, 3),
(7, 3),
(1, 4),
(2, 4),
(4, 4),
(5, 4),
(7, 4),
(8, 4),
(4, 5),
(5, 5),
(4, 6),
(5, 6),
(2, 7),
(3, 7),
(4, 7),
(5, 7),
(6, 7),
(7, 7),
]
.into_iter()
}
static CHARDATA: [[u8; 8]; 128] = [
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0000 (nul)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0001
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0002
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0003
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0004
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0005
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0006
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0007
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0008
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0009
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+000A
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+000B
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+000C
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+000D
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+000E
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+000F
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0010
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0011
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0012
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0013
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0014
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0015
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0016
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0017
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0018
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0019
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+001A
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+001B
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+001C
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+001D
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+001E
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+001F
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0020 (space)
[0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00], // U+0021 (!)
[0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0022 (")
[0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00], // U+0023 (#)
[0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00], // U+0024 ($)
[0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00], // U+0025 (%)
[0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00], // U+0026 (&)
[0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0027 (')
[0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00], // U+0028 (()
[0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00], // U+0029 ())
[0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00], // U+002A (*)
[0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00], // U+002B (+)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06], // U+002C (,)
[0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00], // U+002D (-)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00], // U+002E (.)
[0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00], // U+002F (/)
[0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00], // U+0030 (0)
[0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00], // U+0031 (1)
[0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00], // U+0032 (2)
[0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00], // U+0033 (3)
[0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00], // U+0034 (4)
[0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00], // U+0035 (5)
[0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00], // U+0036 (6)
[0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00], // U+0037 (7)
[0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00], // U+0038 (8)
[0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00], // U+0039 (9)
[0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00], // U+003A (:)
[0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06], // U+003B (;)
[0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00], // U+003C (<)
[0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00], // U+003D (=)
[0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00], // U+003E (>)
[0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00], // U+003F (?)
[0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00], // U+0040 (@)
[0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00], // U+0041 (A)
[0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00], // U+0042 (B)
[0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00], // U+0043 (C)
[0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00], // U+0044 (D)
[0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00], // U+0045 (E)
[0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00], // U+0046 (F)
[0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00], // U+0047 (G)
[0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00], // U+0048 (H)
[0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], // U+0049 (I)
[0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00], // U+004A (J)
[0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00], // U+004B (K)
[0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00], // U+004C (L)
[0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00], // U+004D (M)
[0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00], // U+004E (N)
[0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00], // U+004F (O)
[0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00], // U+0050 (P)
[0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00], // U+0051 (Q)
[0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00], // U+0052 (R)
[0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00], // U+0053 (S)
[0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], // U+0054 (T)
[0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00], // U+0055 (U)
[0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00], // U+0056 (V)
[0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00], // U+0057 (W)
[0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00], // U+0058 (X)
[0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00], // U+0059 (Y)
[0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00], // U+005A (Z)
[0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00], // U+005B ([)
[0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00], // U+005C (\)
[0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00], // U+005D (])
[0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00], // U+005E (^)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF], // U+005F (_)
[0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00], // U+0060 (`)
[0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00], // U+0061 (a)
[0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00], // U+0062 (b)
[0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00], // U+0063 (c)
[0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00], // U+0064 (d)
[0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00], // U+0065 (e)
[0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00], // U+0066 (f)
[0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F], // U+0067 (g)
[0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00], // U+0068 (h)
[0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], // U+0069 (i)
[0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E], // U+006A (j)
[0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00], // U+006B (k)
[0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00], // U+006C (l)
[0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00], // U+006D (m)
[0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00], // U+006E (n)
[0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00], // U+006F (o)
[0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F], // U+0070 (p)
[0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78], // U+0071 (q)
[0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00], // U+0072 (r)
[0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00], // U+0073 (s)
[0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00], // U+0074 (t)
[0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00], // U+0075 (u)
[0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00], // U+0076 (v)
[0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00], // U+0077 (w)
[0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00], // U+0078 (x)
[0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F], // U+0079 (y)
[0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00], // U+007A (z)
[0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00], // U+007B ([)
[0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00], // U+007C (|)
[0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00], // U+007D (])
[0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+007E (~)
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // U+007F
];
fn char(c: char) -> impl Iterator<Item = (u32, u32)> {
let id = c as usize;
CHARDATA[id]
.iter()
.enumerate()
.flat_map(|(y, &d)| {
(0..8).filter_map(move |x: u32| {
if (d >> x) & 1 == 1 {
Some((x, y as u32))
} else {
None
}
})
})
.map(|(x, y)| (x + 1, y + 1))
}

View file

@ -0,0 +1,137 @@
mod image;
mod print;
use crate::prelude::*;
use ::image::RgbaImage;
pub use image::image_grid;
use std::collections::HashMap;
pub trait Visualize {
fn visualize(&self) -> Visualization;
fn print_visualization(&self) {
let v = self.visualize();
print::print(v);
}
fn png_visualization(&self, file: impl AsRef<std::path::Path>) {
let v = self.visualize();
let mut img = RgbaImage::new((v.size.x * 10) as u32, (v.size.y * 10) as u32);
image::draw(&v, &mut img);
let mut f = std::fs::File::create(file).unwrap();
let _ = img.write_to(&mut f, ::image::ImageFormat::Png);
}
}
pub enum Symbol {
Arrow(Direction),
ArrowEnter(Direction),
ArrowExit(Direction),
Char(char),
Block,
Space,
}
impl Symbol {
fn get_char(&self) -> char {
match self {
Symbol::Arrow(dir) => match dir {
Direction::Up => '↑',
Direction::Right => '→',
Direction::Down => '↓',
Direction::Left => '←',
},
Symbol::ArrowEnter(dir) => match dir {
Direction::Up => '↟',
Direction::Right => '↠',
Direction::Down => '↡',
Direction::Left => '↞',
},
Symbol::ArrowExit(dir) => match dir {
Direction::Up => '↥',
Direction::Right => '↦',
Direction::Down => '↧',
Direction::Left => '↤',
},
Symbol::Char(c) => *c,
Symbol::Block => '#',
Symbol::Space => ' ',
}
}
}
pub struct Visualization {
size: Position,
symbols: HashMap<Position, (Symbol, Option<Color>, Option<Color>)>,
}
#[derive(Debug, Clone, Copy)]
pub struct Color {
r: u8,
g: u8,
b: u8,
}
impl Color {
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
pub fn white() -> Self {
Self::new(255, 255, 255)
}
pub fn index(i: usize) -> Self {
let c = [
Color::new(0xe6, 0x00, 0x49),
Color::new(0x0b, 0xb4, 0xff),
Color::new(0x50, 0xe9, 0x91),
Color::new(0xe6, 0xd8, 0x00),
Color::new(0x9b, 0x19, 0xf5),
Color::new(0xff, 0xa3, 0x00),
Color::new(0xdc, 0x0a, 0xb4),
Color::new(0xb3, 0xd4, 0xff),
Color::new(0x00, 0xbf, 0xa0),
];
c[i % c.len()]
}
}
impl Visualization {
pub fn new(size: Position) -> Self {
Self {
size,
symbols: HashMap::new(),
}
}
pub fn add_symbol(
&mut self,
pos: Position,
symbol: Symbol,
fg: Option<Color>,
bg: Option<Color>,
) {
if let Some(s) = self.symbols.get_mut(&pos) {
*s = (symbol, fg, bg);
} else {
self.symbols.insert(pos, (symbol, fg, bg));
}
}
pub fn overwrite_background(&mut self, pos: Position, bg: Option<Color>) {
if let Some(s) = self.symbols.get_mut(&pos) {
s.2 = bg;
} else {
self.symbols.insert(pos, (Symbol::Space, None, bg));
}
}
pub fn size(&self) -> Position {
self.size
}
}

View file

@ -0,0 +1,81 @@
use super::Visualization;
use std::io::Write;
use termcolor::{ColorSpec, StandardStream, WriteColor};
use crate::prelude::*;
pub(super) fn print(v: Visualization) {
let stdout = &mut StandardStream::stdout(termcolor::ColorChoice::Always);
let width_digits = (v.size.x - 1).ilog10() + 1;
let height_digits = (v.size.y - 1).ilog10() + 1;
// print header
for i in 0..width_digits {
let d = width_digits - i - 1;
//padding
for _ in 0..height_digits {
write!(stdout, " ").unwrap();
}
for x in 0..v.size.x {
let digits = x / (i32::pow(10, d));
if digits == 0 && d > 0 {
write!(stdout, " ").unwrap();
} else {
write!(
stdout,
"{}",
char::from_u32((digits % 10) as u32 + 48).unwrap()
)
.unwrap();
}
}
writeln!(stdout).unwrap();
}
for y in 0..v.size.y {
write!(stdout, "{:1$}", y, height_digits as usize).unwrap();
for x in 0..v.size.x {
if let Some((s, fg, bg)) = v.symbols.get(&Position::new(x, y)) {
let mut c = ColorSpec::new();
c.set_fg(fg.as_ref().map(|c| termcolor::Color::Rgb(c.r, c.g, c.b)));
c.set_bg(bg.as_ref().map(|c| termcolor::Color::Rgb(c.r, c.g, c.b)));
stdout.set_color(&c).unwrap();
write!(stdout, "{:1}", s.get_char()).unwrap();
stdout.reset().unwrap();
} else {
write!(stdout, " ").unwrap();
}
}
writeln!(stdout, "{:1$}", y, height_digits as usize).unwrap();
}
for i in 0..width_digits {
let d = width_digits - i - 1;
//padding
for _ in 0..height_digits {
write!(stdout, " ").unwrap();
}
for x in 0..v.size.x {
let digits = x / (i32::pow(10, d));
if digits == 0 && d > 0 {
write!(stdout, " ").unwrap();
} else {
write!(
stdout,
"{}",
char::from_u32((digits % 10) as u32 + 48).unwrap()
)
.unwrap();
}
}
writeln!(stdout).unwrap();
}
}