A simple actor-based simulation of tag, implemented in rust.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
3.9 KiB

use std::convert::TryInto;
use std::error::Error;
use std::io::{stdout, Stdout, Write};
use termion::clear;
use termion::raw::IntoRawMode;
pub use termion::raw::RawTerminal;
use tui::backend::Backend;
pub use tui::backend::TermionBackend;
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use tui::style::{Color, Modifier, Style};
use tui::text::{Span, Spans};
use tui::widgets::canvas::{Canvas, Points};
use tui::widgets::{Block, Borders, Paragraph, Wrap};
use tui::Terminal;
pub struct TerminalView<B: Backend> {
terminal: Terminal<B>,
previous_coords: Vec<(f64, f64)>,
inner_size: Rect,
}
fn get_layout(area: Rect) -> Vec<Rect> {
Layout::default()
.direction(Direction::Horizontal)
.margin(1)
.constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
.split(area)
}
fn get_block() -> Block<'static> {
Block::default().borders(Borders::ALL).title("gntag")
}
fn get_inner_size(terminal: &mut Terminal<impl Backend>) -> Result<Rect, Box<dyn Error>> {
let mut res = None;
terminal.draw(|f| {
let chunks = get_layout(f.size());
res = Some(get_block().inner(chunks[0]));
})?;
res.ok_or_else(|| "Error getting canvas content size".into())
}
impl TerminalView<TermionBackend<RawTerminal<Stdout>>> {
pub fn try_new() -> Result<Self, Box<dyn Error>> {
let mut stdout = stdout().into_raw_mode()?;
write!(stdout, "{}", clear::All)?;
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let inner_size = get_inner_size(&mut terminal)?;
Ok(Self {
terminal,
previous_coords: vec![],
inner_size,
})
}
}
const FACTOR: isize = 3;
impl<B: Backend> TerminalView<B> {
pub fn content_size(&self) -> Result<(isize, isize), Box<dyn Error>> {
let size = self.inner_size;
let width: isize = size.width.try_into()?;
let height: isize = size.height.try_into()?;
Ok((width * FACTOR, height * FACTOR))
}
pub fn draw(
&mut self,
gen: usize,
tagged: (isize, isize),
positions: &[(isize, isize)],
) -> Result<bool, Box<dyn Error>> {
let mut coords = vec![];
for (x, y) in positions {
coords.push((*x as f64, *y as f64));
}
let prev_points = Points {
coords: &self.previous_coords,
color: Color::Black,
};
let cur_points = Points {
coords: &coords,
color: Color::White,
};
let content_size = self.content_size()?;
let mut inner_size = self.inner_size;
self.terminal.draw(|f| {
let chunks = get_layout(f.size());
let block = get_block();
inner_size = block.inner(chunks[0]);
let canvas = Canvas::default()
.block(block)
.paint(|ctx| {
ctx.draw(&prev_points);
ctx.layer();
ctx.draw(&cur_points);
ctx.layer();
ctx.draw(&Points {
coords: &[(tagged.0 as f64, tagged.1 as f64)],
color: Color::Yellow,
});
})
.background_color(Color::Black)
.x_bounds([0.0, content_size.0 as f64])
.y_bounds([0.0, content_size.1 as f64]);
f.render_widget(canvas, chunks[0]);
let text = vec![
Spans::from(vec![
Span::styled(
"Generation: ",
Style::default().add_modifier(Modifier::BOLD),
),
Span::raw(gen.to_string()),
]),
Spans::from(vec![
Span::styled("Agents: ", Style::default().add_modifier(Modifier::BOLD)),
Span::raw(coords.len().to_string()),
]),
];
let side = Paragraph::new(text)
.block(Block::default().title("Info").borders(Borders::ALL))
.style(Style::default().fg(Color::White).bg(Color::Black))
.alignment(Alignment::Center)
.wrap(Wrap { trim: true });
f.render_widget(side, chunks[1]);
})?;
self.previous_coords = coords;
let old_inner_size = self.inner_size;
self.inner_size = inner_size;
Ok(inner_size != old_inner_size)
}
}