Refactor into different crates
This commit is contained in:
parent
94473c64e0
commit
dfdeae5638
82 changed files with 624 additions and 647 deletions
16
factorio-core/Cargo.toml
Normal file
16
factorio-core/Cargo.toml
Normal 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
75
factorio-core/src/aabb.rs
Normal 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))
|
||||
);
|
||||
}
|
||||
}
|
||||
94
factorio-core/src/block.rs
Normal file
94
factorio-core/src/block.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
factorio-core/src/color.rs
Normal file
21
factorio-core/src/color.rs
Normal 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]
|
||||
}
|
||||
}
|
||||
100
factorio-core/src/direction.rs
Normal file
100
factorio-core/src/direction.rs
Normal 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
18
factorio-core/src/lib.rs
Normal 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},
|
||||
};
|
||||
}
|
||||
148
factorio-core/src/pathfield.rs
Normal file
148
factorio-core/src/pathfield.rs
Normal 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(())
|
||||
}
|
||||
74
factorio-core/src/position.rs
Normal file
74
factorio-core/src/position.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
111
factorio-core/src/transformation.rs
Normal file
111
factorio-core/src/transformation.rs
Normal 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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
432
factorio-core/src/visualize/image.rs
Normal file
432
factorio-core/src/visualize/image.rs
Normal 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))
|
||||
}
|
||||
137
factorio-core/src/visualize/mod.rs
Normal file
137
factorio-core/src/visualize/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
81
factorio-core/src/visualize/print.rs
Normal file
81
factorio-core/src/visualize/print.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue