From 70ad3a3bddc0ac0471177dde563ab14792fd91a5 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 22 Jul 2021 16:44:16 +0200 Subject: [PATCH 01/15] Add initial README --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..74a701a --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# gntag + +gntag (from German »Guten Tag«: »good day«, roughly equivalent to »g'day«) is an actor-based simulation of [tag](https://en.wikipedia.org/wiki/Tag_(game)) implemented in rust. + +## Running + +```sh +git clone https://git.adrianheine.de/adrian/gntag.git +cd gntag +cargo run +``` + +To exit the simulation, press q, ESC or Ctrl+c. From 2f06f9b0b7ae40dca11b6125ca27a3da767e6c36 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 22 Jul 2021 16:45:45 +0200 Subject: [PATCH 02/15] Somewhat structure main.rs --- src/main.rs | 92 ++++++++++++++++++++++++++++------------------------- src/view.rs | 3 +- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/main.rs b/src/main.rs index b21a9c9..518c107 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use gntag::agent::{Agent, SimpleAgent}; -use gntag::view::TerminalView; +use gntag::view::{Backend, TerminalView}; use gntag::world::ActualWorld; use std::io; use std::io::Read; @@ -9,6 +9,8 @@ use std::thread; fn main() { let view = Arc::new(Mutex::new(Some(TerminalView::try_new().unwrap()))); + + // Exit on q, ESC and Ctrl-C and reset terminal let view2 = view.clone(); thread::spawn(move || { let stdin = io::stdin(); @@ -22,48 +24,52 @@ fn main() { } } }); - loop { - let (width, height) = (*view.lock().unwrap()) - .as_mut() - .unwrap() - .content_size() - .unwrap(); - let mut agents: Vec<(_, Box)> = vec![]; - for x in (0..width).step_by(10) { - for y in (0..height).step_by(10) { - agents.push(((x, y).into(), Box::new(SimpleAgent))); - } - } - let mut world = ActualWorld::new((width, height).into(), agents); - let mut gen = 0; - loop { - let resized = (*view.lock().unwrap()) - .as_mut() - .unwrap() - .draw( - gen, - world - .state - .agent_positions - .get(&world.state.tagged) - .map(|pos| (pos.x, pos.y)) - .unwrap(), - world - .state - .agent_positions - .iter() - .map(|(_id, pos)| (pos.x, pos.y)) - .collect::>() - .as_ref(), - ) - .unwrap(); - if resized { - break; - }; - world.do_step(); - gen += 1; - //std::thread::sleep(std::time::Duration::from_millis(500)); - } + loop { + run_simulation(&view); + } +} + +fn run_simulation(view: &Arc>>>) { + let (width, height) = (*view.lock().unwrap()) + .as_mut() + .unwrap() + .content_size() + .unwrap(); + let mut agents: Vec<(_, Box)> = vec![]; + for x in (0..width).step_by(10) { + for y in (0..height).step_by(10) { + agents.push(((x, y).into(), Box::new(SimpleAgent))); + } + } + let mut world = ActualWorld::new((width, height).into(), agents); + + let mut gen = 0; + loop { + let resized = (*view.lock().unwrap()) + .as_mut() + .unwrap() + .draw( + gen, + world + .state + .agent_positions + .get(&world.state.tagged) + .map(|pos| (pos.x, pos.y)) + .unwrap(), + world + .state + .agent_positions + .iter() + .map(|(_id, pos)| (pos.x, pos.y)) + .collect::>() + .as_ref(), + ) + .unwrap(); + if resized { + return; + }; + world.do_step(); + gen += 1; } } diff --git a/src/view.rs b/src/view.rs index 320eaee..5fb7290 100644 --- a/src/view.rs +++ b/src/view.rs @@ -3,7 +3,8 @@ use std::error::Error; use std::io::{stdout, Stdout, Write}; use termion::clear; use termion::raw::{IntoRawMode, RawTerminal}; -use tui::backend::{Backend, TermionBackend}; +pub use tui::backend::Backend; +use tui::backend::TermionBackend; use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use tui::style::{Color, Modifier, Style}; use tui::text::{Span, Spans}; From 3b1db890b724e7d79e85165cef979e6db83b94b1 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 22 Jul 2021 17:55:50 +0200 Subject: [PATCH 03/15] Fix tests --- src/world.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/world.rs b/src/world.rs index 9c1ecc4..3122c05 100644 --- a/src/world.rs +++ b/src/world.rs @@ -141,8 +141,8 @@ mod test { vec![(Position { x: 0, y: 0 }, Box::new(NullAgent))], ); world.do_step(); - assert_eq!(world.tagged, 0); - assert_eq!(world.tagged_by, None); + assert_eq!(world.state.tagged, 0); + assert_eq!(world.state.tagged_by, None); } #[test] @@ -161,8 +161,8 @@ mod test { ], ); world.do_step(); - assert_eq!(world.tagged, 0); - assert_eq!(world.tagged_by, Some(1)); + assert_eq!(world.state.tagged, 0); + assert_eq!(world.state.tagged_by, Some(1)); } #[test] From 6eb184c6ae9f1f27fde81f633100becdb508690d Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 22 Jul 2021 20:08:43 +0200 Subject: [PATCH 04/15] Add missing move checks --- src/world.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/world.rs b/src/world.rs index 3122c05..59f46d5 100644 --- a/src/world.rs +++ b/src/world.rs @@ -102,7 +102,10 @@ impl ActualWorld { match mv { Move::Noop => {} Move::TryTag(other_id) => { - // FIXME check distance + let my_pos = self.state.agent_positions.get(&id).unwrap(); + let other_pos = self.state.agent_positions.get(&other_id).unwrap(); + assert!((my_pos.x - other_pos.x).abs() <= 1); + assert!((my_pos.y - other_pos.y).abs() <= 1); assert_eq!(self.state.tagged, id); assert_ne!(self.state.tagged_by, Some(*other_id)); assert!( @@ -111,7 +114,8 @@ impl ActualWorld { ); } Move::TryMove(dir) => { - // FIXME check speed + assert!(dir.x.abs() <= 1); + assert!(dir.y.abs() <= 1); let pos = self.state.agent_positions.get(&id).unwrap(); let size = &self.size; assert!(pos.x + dir.x > 0); From 25b0004822cbd34f38334bcd26a198c84b37744c Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 22 Jul 2021 22:48:02 +0200 Subject: [PATCH 05/15] Add benchmark --- Cargo.toml | 7 +++++++ benches/benchmark.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 +- src/world.rs | 18 +++++++++++++---- 4 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 benches/benchmark.rs diff --git a/Cargo.toml b/Cargo.toml index 83dc44d..ca0c338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,10 @@ authors = ["Adrian Heine "] [dependencies] tui = "0.15" termion = "1.5" + +[dev-dependencies] +criterion = "0.3" + +[[bench]] +name = "benchmark" +harness = false diff --git a/benches/benchmark.rs b/benches/benchmark.rs new file mode 100644 index 0000000..cc2ed77 --- /dev/null +++ b/benches/benchmark.rs @@ -0,0 +1,48 @@ +use criterion::{BenchmarkId, measurement::Measurement}; +use criterion::{criterion_group, criterion_main, Criterion, Throughput, BenchmarkGroup}; +use gntag::agent::{Agent, SimpleAgent}; +use gntag::world::ActualWorld; + +fn world(mut group: BenchmarkGroup, validate: bool) { + let width: isize = 1000; + for spacing in (50..100).step_by(10) { + group.throughput(Throughput::Elements((width / spacing).pow(2) as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(spacing), + &spacing, + |b, &spacing| { + let mut agents: Vec<(_, Box)> = vec![]; + for x in (0..width).step_by(spacing as usize) { + for y in (0..width).step_by(spacing as usize) { + agents.push(((x, y).into(), Box::new(SimpleAgent))); + } + } + let mut world = ActualWorld::new((width, width).into(), agents, validate); + b.iter(|| world.do_step()); + }, + ); + } + group.finish(); +} + +fn world_validating(c: &mut Criterion) { + let group = c.benchmark_group("world_validating"); + world(group, true) +} + +fn world_nonvalidating(c: &mut Criterion) { + let group = c.benchmark_group("world_nonvalidating"); + world(group, false) +} + +criterion_group!(benches, world_validating, world_nonvalidating); +criterion_main!(benches); + +// 5000/1000 -> 9,993 +// 5/01 -> 23,252 +// 50/10 -> 14,000 +// 100/20 -> 14,000 +// 100/10 -> 96,000 +// 100/05 -> 917,767 +// 200/10 -> 916,172 +// 1000/10 -> 443,482,526 diff --git a/src/main.rs b/src/main.rs index 518c107..8cb1afe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ fn run_simulation(view: &Arc>>>) { agents.push(((x, y).into(), Box::new(SimpleAgent))); } } - let mut world = ActualWorld::new((width, height).into(), agents); + let mut world = ActualWorld::new((width, height).into(), agents, true); let mut gen = 0; loop { diff --git a/src/world.rs b/src/world.rs index 59f46d5..9eb2c49 100644 --- a/src/world.rs +++ b/src/world.rs @@ -5,6 +5,7 @@ pub struct ActualWorld { size: Position, pub agents: HashMap>, pub state: WorldState, + validating: bool, } pub struct WorldState { @@ -16,7 +17,11 @@ pub struct WorldState { impl ActualWorld { /// Agents receive incrementing ids starting with 0 /// The last agent is 'It' - pub fn new(size: Position, input_agents: Vec<(Position, Box)>) -> Self { + pub fn new( + size: Position, + input_agents: Vec<(Position, Box)>, + validating: bool, + ) -> Self { let agent_count = input_agents.len(); assert!(agent_count > 0); let mut id = 0; @@ -36,6 +41,7 @@ impl ActualWorld { size, agents, state, + validating, } } @@ -77,8 +83,9 @@ impl ActualWorld { tagged_by: self.state.tagged_by, }; for (id, mv) in moves { - self.check_move(id, &mv); - //println!("{} {:?}", id, mv); + if self.validating { + self.check_move(id, &mv); + } let mut new_pos = None; match mv { Move::Noop => {} @@ -135,7 +142,7 @@ mod test { #[test] #[should_panic] fn empty() { - ActualWorld::new(Position { x: 0, y: 0 }, vec![]); + ActualWorld::new(Position { x: 0, y: 0 }, vec![], true); } #[test] @@ -143,6 +150,7 @@ mod test { let mut world = ActualWorld::new( Position { x: 0, y: 0 }, vec![(Position { x: 0, y: 0 }, Box::new(NullAgent))], + true, ); world.do_step(); assert_eq!(world.state.tagged, 0); @@ -163,6 +171,7 @@ mod test { Box::new(ScriptedAgent::new(vec![Move::TryTag(0)])), ), ], + true, ); world.do_step(); assert_eq!(world.state.tagged, 0); @@ -178,6 +187,7 @@ mod test { Position { x: 0, y: 0 }, Box::new(ScriptedAgent::new(vec![Move::TryMove((-1, -1).into())])), )], + true, ); world.do_step(); } From 0ff0126c39a3f896c4173d353e317bb75976dcc5 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 22 Jul 2021 22:48:13 +0200 Subject: [PATCH 06/15] Update readme --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 74a701a..8d3cd2f 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,23 @@ cargo run ``` To exit the simulation, press q, ESC or Ctrl+c. + +## Tests + +A few tests can be run with: + +```sh +cargo test +``` + +## Benchmarks + +`cargo bench` runs benchmarks. Their results are available under `target/criterion/report/index.html`. + +## Simulation + +* The world is a simple two-dimensional rectangle with integer positions +* Agents can occupy the same space +* Agents can make one move per simulation step, either by trying to move by at most one unit in both directions or by trying to tag another actor +* Agents need to be at most one unit away in both directions from others in order to tag them +* Agents act at the same time and their actions are validated against the previous state From 637bec67f84331d1faf7465c801b78bdd7fbfc12 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Thu, 22 Jul 2021 23:52:53 +0200 Subject: [PATCH 07/15] Put different configurations together in benchmark --- benches/benchmark.rs | 48 +++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index cc2ed77..ce51364 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,23 +1,35 @@ -use criterion::{BenchmarkId, measurement::Measurement}; -use criterion::{criterion_group, criterion_main, Criterion, Throughput, BenchmarkGroup}; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use gntag::agent::{Agent, SimpleAgent}; use gntag::world::ActualWorld; -fn world(mut group: BenchmarkGroup, validate: bool) { +fn get_world(width: isize, spacing: usize, validate: bool) -> ActualWorld { + let mut agents = vec![]; + for x in (0..width).step_by(spacing) { + for y in (0..width).step_by(spacing) { + agents.push(((x, y).into(), Box::new(SimpleAgent) as Box)); + } + } + ActualWorld::new((width, width).into(), agents, validate) +} + +fn world(c: &mut Criterion) { + let mut group = c.benchmark_group("world"); let width: isize = 1000; - for spacing in (50..100).step_by(10) { + for spacing in (50..=100).step_by(25) { group.throughput(Throughput::Elements((width / spacing).pow(2) as u64)); group.bench_with_input( - BenchmarkId::from_parameter(spacing), + BenchmarkId::new("validating", spacing), &spacing, |b, &spacing| { - let mut agents: Vec<(_, Box)> = vec![]; - for x in (0..width).step_by(spacing as usize) { - for y in (0..width).step_by(spacing as usize) { - agents.push(((x, y).into(), Box::new(SimpleAgent))); - } - } - let mut world = ActualWorld::new((width, width).into(), agents, validate); + let mut world = get_world(width, spacing as usize, true); + b.iter(|| world.do_step()); + }, + ); + group.bench_with_input( + BenchmarkId::new("non-validating", spacing), + &spacing, + |b, &spacing| { + let mut world = get_world(width, spacing as usize, false); b.iter(|| world.do_step()); }, ); @@ -25,17 +37,7 @@ fn world(mut group: BenchmarkGroup, validate: bool) { group.finish(); } -fn world_validating(c: &mut Criterion) { - let group = c.benchmark_group("world_validating"); - world(group, true) -} - -fn world_nonvalidating(c: &mut Criterion) { - let group = c.benchmark_group("world_nonvalidating"); - world(group, false) -} - -criterion_group!(benches, world_validating, world_nonvalidating); +criterion_group!(benches, world); criterion_main!(benches); // 5000/1000 -> 9,993 From 449644ade6e304429caa2e86d0c6c2e34be9ad29 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 23 Jul 2021 00:57:10 +0200 Subject: [PATCH 08/15] Don't remove previous agent position It might still be used by check_move --- src/agent.rs | 1 + src/world.rs | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 2fdc50b..7c339ed 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1,5 +1,6 @@ pub type Distance = isize; +#[derive(Clone)] pub struct Position { pub x: Distance, pub y: Distance, diff --git a/src/world.rs b/src/world.rs index 9eb2c49..4fe9ada 100644 --- a/src/world.rs +++ b/src/world.rs @@ -87,6 +87,7 @@ impl ActualWorld { self.check_move(id, &mv); } let mut new_pos = None; + let pos = self.state.agent_positions.get(&id).unwrap(); match mv { Move::Noop => {} Move::TryTag(other_id) => { @@ -94,14 +95,12 @@ impl ActualWorld { new_state.tagged_by = Some(id); } Move::TryMove(dir) => { - let pos = self.state.agent_positions.get(&id).unwrap(); new_pos = Some((pos.x + dir.x, pos.y + dir.y).into()); } } - new_state.agent_positions.insert( - id, - new_pos.unwrap_or_else(|| self.state.agent_positions.remove(&id).unwrap()), - ); + new_state + .agent_positions + .insert(id, new_pos.unwrap_or_else(|| pos.clone())); } self.state = new_state; } From 894ebbd736fddb972ee527369bab703f965871a6 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 23 Jul 2021 10:25:28 +0200 Subject: [PATCH 09/15] Don't collect other agents' position for worldview This is a huge performance improvement (drops ~90% of execution time) with the current SimpleAgent implementation, because a SimpleAgent that is not It will not even look at other agents' positions. --- src/agent.rs | 4 ++-- src/world.rs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 7c339ed..260a922 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -26,12 +26,12 @@ impl From<(Distance, Distance)> for Direction { pub type AgentId = usize; -pub struct WorldView { +pub struct WorldView<'o> { pub self_id: AgentId, pub tagged: AgentId, pub tagged_by: Option, pub bounds_distance: (Distance, Distance, Distance, Distance), - pub other_agents: Vec<(Direction, AgentId)>, + pub other_agents: &'o mut dyn Iterator, } #[derive(Debug)] diff --git a/src/world.rs b/src/world.rs index 4fe9ada..581d006 100644 --- a/src/world.rs +++ b/src/world.rs @@ -58,7 +58,7 @@ impl ActualWorld { tagged_by: self.state.tagged_by, tagged: self.state.tagged, bounds_distance: (pos.y, self.size.x - pos.x, self.size.y - pos.y, pos.x), - other_agents: self + other_agents: &mut self .state .agent_positions .iter() @@ -71,8 +71,7 @@ impl ActualWorld { }, *id, ) - }) - .collect(), + }), }), ) }) From 9b92497c3b29655880d2c30484cf3f2ebbad59a4 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 23 Jul 2021 15:10:14 +0200 Subject: [PATCH 10/15] Fix off-by-ones at world's end --- src/agent.rs | 2 +- src/world.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/agent.rs b/src/agent.rs index 260a922..7cd68f7 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -110,7 +110,7 @@ fn random_move_within( loop { let mv = random_move(); if let Move::TryMove(Direction { x, y }) = mv { - if top + y > 0 && bottom - y > 0 && left + x > 0 && right - x > 0 { + if top + y >= 0 && bottom - y >= 0 && left + x >= 0 && right - x >= 0 { return mv; } } diff --git a/src/world.rs b/src/world.rs index 581d006..6633052 100644 --- a/src/world.rs +++ b/src/world.rs @@ -57,7 +57,12 @@ impl ActualWorld { self_id: *id, tagged_by: self.state.tagged_by, tagged: self.state.tagged, - bounds_distance: (pos.y, self.size.x - pos.x, self.size.y - pos.y, pos.x), + bounds_distance: ( + pos.y, + self.size.x - pos.x - 1, + self.size.y - pos.y - 1, + pos.x, + ), other_agents: &mut self .state .agent_positions @@ -123,9 +128,9 @@ impl ActualWorld { assert!(dir.y.abs() <= 1); let pos = self.state.agent_positions.get(&id).unwrap(); let size = &self.size; - assert!(pos.x + dir.x > 0); + assert!(pos.x + dir.x >= 0); assert!(pos.x + dir.x < size.x); - assert!(pos.y + dir.y > 0); + assert!(pos.y + dir.y >= 0); assert!(pos.y + dir.y < size.y); } } From 1892f7e9f5b4d90f1b46be9bee16f8e924b08715 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 23 Jul 2021 15:13:41 +0200 Subject: [PATCH 11/15] Share get_world --- benches/benchmark.rs | 17 +++-------------- src/lib.rs | 13 +++++++++++++ src/main.rs | 11 ++--------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index ce51364..78d990e 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,16 +1,5 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; -use gntag::agent::{Agent, SimpleAgent}; -use gntag::world::ActualWorld; - -fn get_world(width: isize, spacing: usize, validate: bool) -> ActualWorld { - let mut agents = vec![]; - for x in (0..width).step_by(spacing) { - for y in (0..width).step_by(spacing) { - agents.push(((x, y).into(), Box::new(SimpleAgent) as Box)); - } - } - ActualWorld::new((width, width).into(), agents, validate) -} +use gntag::get_world; fn world(c: &mut Criterion) { let mut group = c.benchmark_group("world"); @@ -21,7 +10,7 @@ fn world(c: &mut Criterion) { BenchmarkId::new("validating", spacing), &spacing, |b, &spacing| { - let mut world = get_world(width, spacing as usize, true); + let mut world = get_world(width, width, spacing as usize, true); b.iter(|| world.do_step()); }, ); @@ -29,7 +18,7 @@ fn world(c: &mut Criterion) { BenchmarkId::new("non-validating", spacing), &spacing, |b, &spacing| { - let mut world = get_world(width, spacing as usize, false); + let mut world = get_world(width, width, spacing as usize, false); b.iter(|| world.do_step()); }, ); diff --git a/src/lib.rs b/src/lib.rs index 53a7904..486b5a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,16 @@ pub mod agent; pub mod view; pub mod world; + +use agent::{Agent, SimpleAgent}; +use world::ActualWorld; + +pub fn get_world(width: isize, height: isize, spacing: usize, validating: bool) -> ActualWorld { + let mut agents: Vec<(_, Box)> = vec![]; + for x in (0..width).step_by(spacing) { + for y in (0..height).step_by(spacing) { + agents.push(((x, y).into(), Box::new(SimpleAgent))); + } + } + ActualWorld::new((width, height).into(), agents, validating) +} diff --git a/src/main.rs b/src/main.rs index 8cb1afe..5f38ece 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ -use gntag::agent::{Agent, SimpleAgent}; +use gntag::get_world; use gntag::view::{Backend, TerminalView}; -use gntag::world::ActualWorld; use std::io; use std::io::Read; use std::process::exit; @@ -36,13 +35,7 @@ fn run_simulation(view: &Arc>>>) { .unwrap() .content_size() .unwrap(); - let mut agents: Vec<(_, Box)> = vec![]; - for x in (0..width).step_by(10) { - for y in (0..height).step_by(10) { - agents.push(((x, y).into(), Box::new(SimpleAgent))); - } - } - let mut world = ActualWorld::new((width, height).into(), agents, true); + let mut world = get_world(width, height, 10, true); let mut gen = 0; loop { From b0ebb1bb08f3ae122c1809ee6ba92f1490b37f23 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 23 Jul 2021 15:24:44 +0200 Subject: [PATCH 12/15] Move get_view from main to lib --- src/lib.rs | 27 +++++++++++++++++++++++++++ src/main.rs | 27 +++------------------------ src/view.rs | 7 ++++--- 3 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 486b5a1..c9f7445 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,12 @@ pub mod view; pub mod world; use agent::{Agent, SimpleAgent}; +use std::io; +use std::io::Read; +use std::process::exit; +use std::sync::{Arc, Mutex}; +use std::thread; +use view::{RawTerminal, TerminalView, TermionBackend}; use world::ActualWorld; pub fn get_world(width: isize, height: isize, spacing: usize, validating: bool) -> ActualWorld { @@ -14,3 +20,24 @@ pub fn get_world(width: isize, height: isize, spacing: usize, validating: bool) } ActualWorld::new((width, height).into(), agents, validating) } + +pub type DefaultView = Arc>>>>>; +pub fn get_view() -> DefaultView { + let view = Arc::new(Mutex::new(Some(TerminalView::try_new().unwrap()))); + + // Exit on q, ESC and Ctrl-C and reset terminal + let view2 = view.clone(); + thread::spawn(move || { + let stdin = io::stdin(); + for byte in stdin.bytes().flatten() { + if byte == b'q' || byte == 0x1b || byte == 0x03 { + if let Ok(mut view) = view2.lock() { + *view = None; // drop view + println!("\n"); + } + exit(0); + } + } + }); + view +} diff --git a/src/main.rs b/src/main.rs index 5f38ece..84c9d82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,14 @@ -use gntag::get_world; -use gntag::view::{Backend, TerminalView}; -use std::io; -use std::io::Read; -use std::process::exit; -use std::sync::{Arc, Mutex}; -use std::thread; +use gntag::{get_view, get_world, DefaultView}; fn main() { - let view = Arc::new(Mutex::new(Some(TerminalView::try_new().unwrap()))); - - // Exit on q, ESC and Ctrl-C and reset terminal - let view2 = view.clone(); - thread::spawn(move || { - let stdin = io::stdin(); - for byte in stdin.bytes().flatten() { - if byte == b'q' || byte == 0x1b || byte == 0x03 { - if let Ok(mut view) = view2.lock() { - *view = None; // drop view - println!("\n"); - } - exit(0); - } - } - }); + let view = get_view(); loop { run_simulation(&view); } } -fn run_simulation(view: &Arc>>>) { +fn run_simulation(view: &DefaultView) { let (width, height) = (*view.lock().unwrap()) .as_mut() .unwrap() diff --git a/src/view.rs b/src/view.rs index 5fb7290..8aa8a21 100644 --- a/src/view.rs +++ b/src/view.rs @@ -2,9 +2,10 @@ use std::convert::TryInto; use std::error::Error; use std::io::{stdout, Stdout, Write}; use termion::clear; -use termion::raw::{IntoRawMode, RawTerminal}; -pub use tui::backend::Backend; -use tui::backend::TermionBackend; +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}; From 2ec11dfd6cefc6d34141bd379bfe9e231ec9d73a Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 23 Jul 2021 15:30:25 +0200 Subject: [PATCH 13/15] Extract draw_world --- src/lib.rs | 24 ++++++++++++++++++++++++ src/main.rs | 23 ++--------------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c9f7445..6e4f78e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod view; pub mod world; use agent::{Agent, SimpleAgent}; +use std::error::Error; use std::io; use std::io::Read; use std::process::exit; @@ -41,3 +42,26 @@ pub fn get_view() -> DefaultView { }); view } + +pub fn draw_world( + world: &ActualWorld, + gen: usize, + view: &DefaultView, +) -> Result> { + (*view.lock().unwrap()).as_mut().unwrap().draw( + gen, + world + .state + .agent_positions + .get(&world.state.tagged) + .map(|pos| (pos.x, pos.y)) + .unwrap(), + world + .state + .agent_positions + .iter() + .map(|(_id, pos)| (pos.x, pos.y)) + .collect::>() + .as_ref(), + ) +} diff --git a/src/main.rs b/src/main.rs index 84c9d82..777bc82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use gntag::{get_view, get_world, DefaultView}; +use gntag::{draw_world, get_view, get_world, DefaultView}; fn main() { let view = get_view(); @@ -18,26 +18,7 @@ fn run_simulation(view: &DefaultView) { let mut gen = 0; loop { - let resized = (*view.lock().unwrap()) - .as_mut() - .unwrap() - .draw( - gen, - world - .state - .agent_positions - .get(&world.state.tagged) - .map(|pos| (pos.x, pos.y)) - .unwrap(), - world - .state - .agent_positions - .iter() - .map(|(_id, pos)| (pos.x, pos.y)) - .collect::>() - .as_ref(), - ) - .unwrap(); + let resized = draw_world(&world, gen, view).unwrap(); if resized { return; }; From 4f2f0431d1db1399e0edd04b1544194772bbf21a Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 23 Jul 2021 15:33:28 +0200 Subject: [PATCH 14/15] Document usage as a library --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8d3cd2f..747cf78 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ cargo run To exit the simulation, press q, ESC or Ctrl+c. +## Using as a library + +`src/main.rs` is a pretty minimal example of the currently exposed high-level functionality. `gntag::actor` includes simpler actor iplementations and all the types, `gntag::world` contains `ActualWorld` and `WorldState`. + ## Tests A few tests can be run with: From 22ffcbb1aa18e0ed2c48a266361e8fd525f4d8ff Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Fri, 23 Jul 2021 15:36:50 +0200 Subject: [PATCH 15/15] actor -> agent --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 747cf78..3b11625 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # gntag -gntag (from German »Guten Tag«: »good day«, roughly equivalent to »g'day«) is an actor-based simulation of [tag](https://en.wikipedia.org/wiki/Tag_(game)) implemented in rust. +gntag (from German »Guten Tag«: »good day«, roughly equivalent to »g'day«) is an agent-based simulation of [tag](https://en.wikipedia.org/wiki/Tag_(game)) implemented in rust. ## Running @@ -14,7 +14,7 @@ To exit the simulation, press q, ESC or Ctrl+c. ## Using as a library -`src/main.rs` is a pretty minimal example of the currently exposed high-level functionality. `gntag::actor` includes simpler actor iplementations and all the types, `gntag::world` contains `ActualWorld` and `WorldState`. +`src/main.rs` is a pretty minimal example of the currently exposed high-level functionality. `gntag::agent` includes simpler agent iplementations and all the types, `gntag::world` contains `ActualWorld` and `WorldState`. ## Tests @@ -32,6 +32,6 @@ cargo test * The world is a simple two-dimensional rectangle with integer positions * Agents can occupy the same space -* Agents can make one move per simulation step, either by trying to move by at most one unit in both directions or by trying to tag another actor +* Agents can make one move per simulation step, either by trying to move by at most one unit in both directions or by trying to tag another agent * Agents need to be at most one unit away in both directions from others in order to tag them * Agents act at the same time and their actions are validated against the previous state