7fe895f041
This also forces an update of the rand dependency, so while we are at it we update all the dependencies.
339 lines
10 KiB
Rust
339 lines
10 KiB
Rust
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<i64, Square>);
|
|
impl Board {
|
|
fn bbox(&self) -> BBox2d {
|
|
self.0.bbox()
|
|
}
|
|
}
|
|
|
|
fn send<T: Write>(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<Ordering> {
|
|
self.prio.partial_cmp(&other.prio)
|
|
}
|
|
}
|
|
|
|
impl Ord for Move {
|
|
fn cmp(&self, other: &Move) -> Ordering {
|
|
self.prio.cmp(&other.prio)
|
|
}
|
|
}
|
|
|
|
fn add_move<R>(board: &Board, open: &mut BinaryHeap<Move>, 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::<Vec<_>>();
|
|
eprintln!("executing {}", args[0]);
|
|
|
|
let x_size = args[1].parse::<i64>().unwrap();
|
|
let y_size = args[2].parse::<i64>().unwrap();
|
|
let arg = args[3].parse::<isize>().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);
|
|
}
|
|
}
|