core_rust/core/utils/
plotting.rs

1use crate::core::game::state::State;
2use plotters::prelude::*;
3use std::fs;
4use std::path::Path;
5
6/// Render a world grid to a PNG file at `out_path`.
7#[allow(dead_code)]
8pub fn draw_world(
9    world: &[Vec<char>],
10    out_path: &str,
11    cell_size: u32,
12) -> Result<(), Box<dyn std::error::Error>> {
13    // Ensure output directory exists
14    if let Some(dir) = Path::new(out_path).parent() {
15        fs::create_dir_all(dir)?;
16    }
17
18    // convert sizes to i32
19    let rows = world.len() as i32;
20    let cols = if rows > 0 { world[0].len() as i32 } else { 0 };
21    let cs = cell_size as i32;
22    let width = cols * cs;
23    let height = rows * cs;
24
25    // Create a bitmap backend; note it still takes (u32,u32)
26    let root = BitMapBackend::new(out_path, ((width) as u32, (height) as u32)).into_drawing_area();
27    root.fill(&WHITE)?;
28
29    // draw grid lines
30    for row in 0..=rows {
31        let y = row * cs;
32        root.draw(&PathElement::new(vec![(0, y), (width, y)], BLACK))?;
33    }
34    for col in 0..=cols {
35        let x = col * cs;
36        root.draw(&PathElement::new(vec![(x, 0), (x, height)], BLACK))?;
37    }
38
39    // Draw each cell
40    for (r, row) in world.iter().enumerate() {
41        for (c, &ch) in row.iter().enumerate() {
42            let x0 = (c as i32) * cs;
43            let y0 = (r as i32) * cs;
44
45            match ch {
46                'X' => {
47                    // filled black
48                    root.draw(&Rectangle::new(
49                        [(x0, y0), (x0 + cs, y0 + cs)],
50                        BLACK.filled(),
51                    ))?;
52                }
53                'G' => {
54                    // green "G" centered
55                    root.draw(&Text::new(
56                        "G",
57                        (x0 + cs / 3, y0 + cs / 4),
58                        ("sans-serif", (cs / 2) as u32).into_font().color(&GREEN),
59                    ))?;
60                }
61                _ => { /* leave blank */ }
62            }
63        }
64    }
65
66    root.present()?;
67    Ok(())
68}
69
70/// Render the abstraction overlay on top of a world grid.
71#[allow(dead_code)]
72pub fn draw_abstraction(
73    world: &[Vec<char>],
74    states: &[State],
75    clusters: &[Vec<isize>],
76    out_path: &str,
77    cell_size: u32,
78) -> Result<(), Box<dyn std::error::Error>> {
79    // Ensure the directory exists
80    if let Some(dir) = Path::new(out_path).parent() {
81        fs::create_dir_all(dir)?;
82    }
83    let rows = world.len() as i32;
84    let cols = if rows > 0 { world[0].len() as i32 } else { 0 };
85    let cs = cell_size as i32;
86    let width = (cols * cs) as u32;
87    let height = (rows * cs) as u32;
88
89    let root = BitMapBackend::new(out_path, (width, height)).into_drawing_area();
90    root.fill(&WHITE)?;
91
92    // Grid & terrain exactly same as draw_world
93    for r in 0..=rows {
94        let y = r * cs;
95        root.draw(&PathElement::new(vec![(0, y), (cols * cs, y)], BLACK))?;
96    }
97    for c in 0..=cols {
98        let x = c * cs;
99        root.draw(&PathElement::new(vec![(x, 0), (x, rows * cs)], BLACK))?;
100    }
101    for (r, row) in world.iter().enumerate() {
102        for (c, &ch) in row.iter().enumerate() {
103            let x0 = (c as i32) * cs;
104            let y0 = (r as i32) * cs;
105            if ch == 'X' {
106                root.draw(&Rectangle::new(
107                    [(x0, y0), (x0 + cs, y0 + cs)],
108                    BLACK.filled(),
109                ))?;
110            }
111        }
112    }
113
114    // Now overlay cluster IDs as circles with labels
115    for (abs_id, cluster) in clusters.iter().enumerate() {
116        let label = abs_id.to_string();
117        for &state_idx in cluster {
118            let State {
119                unit_position: (x, y),
120                ..
121            } = &states[state_idx as usize];
122            let x0 = (*x as i32) * cs;
123            let y0 = (*y as i32) * cs;
124            let cx = x0 + cs / 2;
125            let cy = y0 + cs / 2;
126            // radius just a bit smaller than half a cell
127            let radius = (cs as f64) * 0.4;
128
129            // lightly‐filled blue circle with thin border
130            root.draw(&Circle::new((cx, cy), radius, BLUE))?;
131
132            // then the cluster‐ID itself, in BLACK, roughly centered
133            root.draw(&Text::new(
134                label.clone(),
135                (
136                    cx - (cs as f64 * 0.15) as i32,
137                    cy - (cs as f64 * 0.15) as i32,
138                ),
139                ("sans-serif", (cs as f64 * 0.5) as u32)
140                    .into_font()
141                    .color(&BLACK),
142            ))?;
143        }
144    }
145
146    root.present()?;
147    Ok(())
148}