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

  1. use std::convert::TryInto;
  2. use std::error::Error;
  3. use std::io::{stdout, Stdout, Write};
  4. use termion::clear;
  5. use termion::raw::IntoRawMode;
  6. pub use termion::raw::RawTerminal;
  7. use tui::backend::Backend;
  8. pub use tui::backend::TermionBackend;
  9. use tui::layout::{Alignment, Constraint, Direction, Layout, Rect};
  10. use tui::style::{Color, Modifier, Style};
  11. use tui::text::{Span, Spans};
  12. use tui::widgets::canvas::{Canvas, Points};
  13. use tui::widgets::{Block, Borders, Paragraph, Wrap};
  14. use tui::Terminal;
  15. pub struct TerminalView<B: Backend> {
  16. terminal: Terminal<B>,
  17. previous_coords: Vec<(f64, f64)>,
  18. inner_size: Rect,
  19. }
  20. fn get_layout(area: Rect) -> Vec<Rect> {
  21. Layout::default()
  22. .direction(Direction::Horizontal)
  23. .margin(1)
  24. .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
  25. .split(area)
  26. }
  27. fn get_block() -> Block<'static> {
  28. Block::default().borders(Borders::ALL).title("gntag")
  29. }
  30. fn get_inner_size(terminal: &mut Terminal<impl Backend>) -> Result<Rect, Box<dyn Error>> {
  31. let mut res = None;
  32. terminal.draw(|f| {
  33. let chunks = get_layout(f.size());
  34. res = Some(get_block().inner(chunks[0]));
  35. })?;
  36. res.ok_or_else(|| "Error getting canvas content size".into())
  37. }
  38. impl TerminalView<TermionBackend<RawTerminal<Stdout>>> {
  39. pub fn try_new() -> Result<Self, Box<dyn Error>> {
  40. let mut stdout = stdout().into_raw_mode()?;
  41. write!(stdout, "{}", clear::All)?;
  42. let backend = TermionBackend::new(stdout);
  43. let mut terminal = Terminal::new(backend)?;
  44. let inner_size = get_inner_size(&mut terminal)?;
  45. Ok(Self {
  46. terminal,
  47. previous_coords: vec![],
  48. inner_size,
  49. })
  50. }
  51. }
  52. const FACTOR: isize = 3;
  53. impl<B: Backend> TerminalView<B> {
  54. pub fn content_size(&self) -> Result<(isize, isize), Box<dyn Error>> {
  55. let size = self.inner_size;
  56. let width: isize = size.width.try_into()?;
  57. let height: isize = size.height.try_into()?;
  58. Ok((width * FACTOR, height * FACTOR))
  59. }
  60. pub fn draw(
  61. &mut self,
  62. gen: usize,
  63. tagged: (isize, isize),
  64. positions: &[(isize, isize)],
  65. ) -> Result<bool, Box<dyn Error>> {
  66. let mut coords = vec![];
  67. for (x, y) in positions {
  68. coords.push((*x as f64, *y as f64));
  69. }
  70. let prev_points = Points {
  71. coords: &self.previous_coords,
  72. color: Color::Black,
  73. };
  74. let cur_points = Points {
  75. coords: &coords,
  76. color: Color::White,
  77. };
  78. let content_size = self.content_size()?;
  79. let mut inner_size = self.inner_size;
  80. self.terminal.draw(|f| {
  81. let chunks = get_layout(f.size());
  82. let block = get_block();
  83. inner_size = block.inner(chunks[0]);
  84. let canvas = Canvas::default()
  85. .block(block)
  86. .paint(|ctx| {
  87. ctx.draw(&prev_points);
  88. ctx.layer();
  89. ctx.draw(&cur_points);
  90. ctx.layer();
  91. ctx.draw(&Points {
  92. coords: &[(tagged.0 as f64, tagged.1 as f64)],
  93. color: Color::Yellow,
  94. });
  95. })
  96. .background_color(Color::Black)
  97. .x_bounds([0.0, content_size.0 as f64])
  98. .y_bounds([0.0, content_size.1 as f64]);
  99. f.render_widget(canvas, chunks[0]);
  100. let text = vec![
  101. Spans::from(vec![
  102. Span::styled(
  103. "Generation: ",
  104. Style::default().add_modifier(Modifier::BOLD),
  105. ),
  106. Span::raw(gen.to_string()),
  107. ]),
  108. Spans::from(vec![
  109. Span::styled("Agents: ", Style::default().add_modifier(Modifier::BOLD)),
  110. Span::raw(coords.len().to_string()),
  111. ]),
  112. ];
  113. let side = Paragraph::new(text)
  114. .block(Block::default().title("Info").borders(Borders::ALL))
  115. .style(Style::default().fg(Color::White).bg(Color::Black))
  116. .alignment(Alignment::Center)
  117. .wrap(Wrap { trim: true });
  118. f.render_widget(side, chunks[1]);
  119. })?;
  120. self.previous_coords = coords;
  121. let old_inner_size = self.inner_size;
  122. self.inner_size = inner_size;
  123. Ok(inner_size != old_inner_size)
  124. }
  125. }