pixelfoo-apps/src/bin/maze/main.rs
Juergen Stuber 7fe895f041 Replace the local implementation of 2d geometry by lowdim
This also forces an update of the rand dependency,
so while we are at it we update all the dependencies.
2023-04-10 01:00:41 +02:00

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);
}
}