1use crate::{Point, SweeperBuilder};
2
3#[derive(thiserror::Error, Debug)]
4pub enum LoaderError {
5 #[error("IO error")]
6 Io(#[from] std::io::Error),
7 #[error("Inner error")]
8 Inner(#[from] Box<dyn std::error::Error>),
9}
10
11pub trait Loader {
14 fn load(&mut self, source: &str) -> Result<SweeperBuilder, LoaderError>;
15}
16
17#[derive(Default)]
19pub struct PlainFileLoader {}
20
21#[derive(Default)]
22enum ParseState {
23 #[default]
24 Polygon,
25 Hole,
26 Steiner,
27}
28
29impl Loader for PlainFileLoader {
30 fn load(&mut self, path: &str) -> Result<SweeperBuilder, LoaderError> {
31 let mut f = std::fs::File::options().read(true).open(path)?;
32 let mut value = "".to_string();
33 std::io::Read::read_to_string(&mut f, &mut value).unwrap();
34
35 let mut state = ParseState::default();
36 let mut polygon = vec![];
37 let mut holes = Vec::<Vec<Point>>::new();
38 let mut steiner_points = Vec::<Point>::new();
39
40 for line in value.lines() {
41 if line.eq("HOLE") {
42 state = ParseState::Hole;
43 holes.push(vec![]);
44 continue;
45 } else if line.eq("STEINER") {
46 state = ParseState::Steiner;
47 continue;
48 }
49 let Some(point) = parse_point(line)? else {
50 continue
51 };
52
53 match state {
54 ParseState::Polygon => {
55 polygon.push(point);
56 }
57 ParseState::Hole => {
58 let current_hole = holes.last_mut().unwrap();
59 current_hole.push(point);
60 }
61 ParseState::Steiner => {
62 steiner_points.push(point);
63 }
64 }
65 }
66
67 Ok(SweeperBuilder::new(polygon)
68 .add_holes(holes)
69 .add_steiner_points(steiner_points))
70 }
71}
72
73fn parse_point(line: &str) -> Result<Option<Point>, LoaderError> {
74 if line.is_empty() {
75 return Ok(None);
76 }
77 let mut iter = line.split_whitespace();
78 let x = iter.next().unwrap();
79 let y = iter.next().unwrap();
80 let x = x.parse::<f64>().unwrap();
81 let y = y.parse::<f64>().unwrap();
82
83 Ok(Some(Point::new(x, y)))
84}