use std::cmp::Ordering; use std::collections::BinaryHeap; use std::env::args; use std::io::stdout; use std::io::Write; use std::thread::sleep; use std::time::Duration; use chrono::Local; use chrono::Timelike; use rand::thread_rng; use rand::Rng; use lowdim::bb2d; use lowdim::p2d; use lowdim::v2d; use lowdim::Array2d; use lowdim::BBox2d; use lowdim::Point2d; use lowdim::Vec2d; use lowdim::Vector; use pixelfoo::color::Color; #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Square { Unused, Unknown { prio: i32 }, Corridor, Wall, Start, Finish, } impl Square { pub fn is_unknown(&self) -> bool { match self { Square::Unknown { .. } => true, _ => false, } } } #[derive(Clone, Debug)] struct Board(Array2d); impl Board { fn bbox(&self) -> BBox2d { self.0.bbox() } } fn send(w: &mut T, board: &Board) -> std::io::Result<()> { for y in board.bbox().y_range() { for x in board.bbox().x_range() { let square = board.0[p2d(x, y)]; let c = match square { Square::Unused => Color::black(), Square::Unknown { prio } => { if prio == 0 { Color::black() } else if prio < 0 { Color::lightblue() } else { Color::darkyellow() } } Square::Corridor => Color::yellow(), Square::Wall => Color::darkblue(), Square::Start => Color::red(), Square::Finish => Color::green(), }; w.write_all(&c.rgb())?; } } w.flush() } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Orientation { Horizontal, Vertical, } impl Board { fn new(board_bbox: BBox2d, maze_bbox: BBox2d) -> Board { Board(Array2d::with(board_bbox, |p| { if maze_bbox.contains(&p) { let x = p.x(); let y = p.y(); if x == maze_bbox.x_min() || x == maze_bbox.x_max() || y == maze_bbox.y_min() || y == maze_bbox.y_max() || (x % 2 == 0 && y % 2 == 0) { Square::Wall } else { Square::Unknown { prio: 0 } } } else { Square::Unused } })) } fn get(&self, pos: Point2d) -> Square { self.0[pos] } fn set(&mut self, pos: Point2d, sq: Square) { self.0[pos] = sq; } fn set_orientation(&mut self, desired: Orientation, pos: Point2d) { let sq = self.get(pos); match sq { Square::Unknown { .. } => { let prio; if pos.x() % 2 == 0 && pos.y() % 2 != 0 { // horizontal corridor, vertical wall prio = if desired == Orientation::Horizontal { 10 } else { -10 }; } else if pos.x() % 2 != 0 && pos.y() % 2 == 0 { // vertical corridor, horizontal wall prio = if desired == Orientation::Vertical { 10 } else { -10 }; } else { // always corridor at the end prio = 0; } self.set(pos, Square::Unknown { prio }); } _ => (), } } fn set_horizontal(&mut self, pos: Point2d) { self.set_orientation(Orientation::Horizontal, pos); } fn set_vertical(&mut self, pos: Point2d) { self.set_orientation(Orientation::Vertical, pos); } fn draw_horizontal_segment(&mut self, pos: Point2d, size: Vec2d) { for x in 0..size.x() { let xn = size.x() - 1 - x; self.set_horizontal(pos + v2d(x, 0)); for y in 1..x.min(xn).min(size.y() / 2) + 1 { self.set_horizontal(pos + v2d(x, y)); self.set_horizontal(pos + v2d(x, -y)); } } } fn draw_vertical_segment(&mut self, pos: Point2d, size: Vec2d) { for y in 0..size.y() { let yn = size.y() - 1 - y; self.set_vertical(pos + v2d(0, y)); for x in 1..y.min(yn).min(size.x() / 2) + 1 { self.set_vertical(pos + v2d(x, y)); self.set_vertical(pos + v2d(-x, y)); } } } fn draw_7_segments(&mut self, pos: Point2d, size: Vec2d, segments: u8) { let length = size.x(); let width = size.y(); let delta = length + 1; let hsize = v2d(length, width); let vsize = v2d(width, length); if (segments & (1 << 0)) != 0 { self.draw_horizontal_segment(pos + v2d(1, 0), hsize); } if (segments & (1 << 1)) != 0 { self.draw_vertical_segment(pos + v2d(delta, 1), vsize); } if (segments & (1 << 2)) != 0 { self.draw_vertical_segment(pos + v2d(delta, delta + 1), vsize); } if (segments & (1 << 3)) != 0 { self.draw_horizontal_segment(pos + v2d(1, 2 * delta), hsize); } if (segments & (1 << 4)) != 0 { self.draw_vertical_segment(pos + v2d(0, delta + 1), vsize); } if (segments & (1 << 5)) != 0 { self.draw_vertical_segment(pos + v2d(0, 1), vsize); } if (segments & (1 << 6)) != 0 { self.draw_horizontal_segment(pos + v2d(1, delta), hsize); } } fn draw_digit(&mut self, pos: Point2d, size: Vec2d, digit: u8) { let segment_table = vec![0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f]; let segments = segment_table[digit as usize]; self.draw_7_segments(pos, size, segments); } } #[derive(Debug)] struct Move { from: Point2d, dir: Vec2d, prio: i32, } impl PartialEq for Move { fn eq(&self, other: &Move) -> bool { self.prio == other.prio } } impl Eq for Move {} impl PartialOrd for Move { fn partial_cmp(&self, other: &Move) -> Option { self.prio.partial_cmp(&other.prio) } } impl Ord for Move { fn cmp(&self, other: &Move) -> Ordering { self.prio.cmp(&other.prio) } } fn add_move(board: &Board, open: &mut BinaryHeap, rng: &mut R, from: Point2d, dir: Vec2d) where R: Rng, { if let Square::Unknown { prio } = board.get(from + dir) { open.push(Move { from, dir, prio: prio * 100 + rng.gen_range(0..1000), }); } } const DEFAULT_ARG: isize = 16; fn main() -> std::io::Result<()> { let args = args().collect::>(); eprintln!("executing {}", args[0]); let x_size = args[1].parse::().unwrap(); let y_size = args[2].parse::().unwrap(); let arg = args[3].parse::().unwrap_or(DEFAULT_ARG); eprintln!("screen size {}x{}, arg {}", x_size, y_size, arg); let mut rng = thread_rng(); let t_frame = 0.040; // s let delay = Duration::new(0, (1_000_000_000.0 * t_frame) as u32); let board_bbox = bb2d(0..x_size, 0..y_size); // round down to odd size for maze let maze_bbox = bb2d(0..(x_size - 1) / 2 * 2 + 1, 0..(y_size - 1) / 2 * 2 + 1); // pick odd position at or near the middle let maze_mid = maze_bbox.center(); let mut board = Board::new(board_bbox, maze_bbox); let mut open = BinaryHeap::new(); let mut last_drawn_minute = 99; loop { if open.is_empty() { // get time let dt = Local::now(); let h = dt.hour(); let m = dt.minute(); if m != last_drawn_minute { last_drawn_minute = m; board = Board::new(board_bbox, maze_bbox); // draw time in prios let segment_size = v2d(11, 5); board.draw_digit(p2d(6, 8), segment_size, (h / 10) as u8); board.draw_digit(p2d(24, 8), segment_size, (h % 10) as u8); board.draw_digit(p2d(42, 8), segment_size, (m / 10) as u8); board.draw_digit(p2d(60, 8), segment_size, (m % 10) as u8); // start in the middle board.set(maze_mid, Square::Corridor); add_move(&board, &mut open, &mut rng, maze_mid, v2d(1, 0)); add_move(&board, &mut open, &mut rng, maze_mid, v2d(0, 1)); add_move(&board, &mut open, &mut rng, maze_mid, v2d(-1, 0)); add_move(&board, &mut open, &mut rng, maze_mid, v2d(0, -1)); } } // draw maze let work = arg.max(1); let mut count = 0; while !open.is_empty() && count < work { let Move { from: p0, dir: dir0, .. } = open.pop().unwrap(); let p1 = p0 + dir0; let p2 = p1 + dir0; if board.get(p1).is_unknown() { board.set(p1, Square::Corridor); board.set(p2, Square::Corridor); for dir1 in Vec2d::unit_vecs_l1() { let p3 = p2 + dir1; let p4 = p3 + dir1; if board.get(p3).is_unknown() { if board.get(p4).is_unknown() { add_move(&board, &mut open, &mut rng, p2, dir1); } else { board.set(p3, Square::Wall); } } } count += 1; } if open.is_empty() { board.set(maze_bbox.min() + v2d(1, 1), Square::Start); board.set(maze_bbox.max() - v2d(1, 1), Square::Finish); } } let mut buf = Vec::with_capacity(board_bbox.area() * 3); send(&mut buf, &board)?; stdout().write_all(&buf)?; stdout().flush()?; sleep(delay); } }