/* * Emyzelium (Rust) * * is another wrapper around ZeroMQ's Publish-Subscribe messaging pattern * with mandatory Curve security and optional ZAP authentication filter, * over Tor, through Tor SOCKS proxy, * for distributed artificial elife, decision making etc. systems where * each peer, identified by its public key, onion address, and port, * publishes and updates vectors of vectors of bytes of data * under unique topics that other peers can subscribe to * and receive the respective data. * * https://github.com/emyzelium/emyzelium-rs * * emyzelium@protonmail.com * * Copyright (c) 2023-2024 Emyzelium caretakers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * Demo */ extern crate crossterm; extern crate rand; extern crate emyzelium; use crossterm::{ cursor::{ self, MoveTo }, event::{ self, Event, KeyCode }, execute, style::{ Color, Colors, Print, SetColors }, terminal::{ self, Clear, ClearType, }, queue }; use emyzelium::{ self as emz, Efunguz }; use rand::prelude::*; use std::{ io::{ stdout, Write }, collections::HashSet, env, time::{ Duration, SystemTime, UNIX_EPOCH } }; // Of course, person_SECRETKEY should be known only to that person // Here they are "revealed" at once for demo purpose const ALIEN_SECRETKEY: &str = "gr6Y.04i(&Y27ju0g7m0HvhG0:rDmx>, birth: HashSet, survival: HashSet, autoemit_interval: f64, framerate: i32, i_turn: u64, cursor_y: i16, cursor_x: i16, others: Vec } fn time_musec() -> i64 { match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(d) => d.as_micros() as i64, Err(_) => 0 } } fn init_term_graphics() -> Result<(), String> { terminal::enable_raw_mode().map_err(|err| err.to_string())?; let _ = execute!(stdout(), terminal::EnterAlternateScreen, terminal::DisableLineWrap, cursor::Hide ); Ok(()) } fn drop_term_graphics() -> Result<(), String> { let _ = execute!(stdout(), cursor::Show, terminal::EnableLineWrap, terminal::LeaveAlternateScreen ); terminal::disable_raw_mode().map_err(|err| err.to_string()) } fn clear_row_right() { let _ = queue!(stdout(), Clear(ClearType::UntilNewLine) ); } fn print_str(y: i16, x: i16, s: &str, fc: Color, bc: Color) { let _ = queue!(stdout(), MoveTo(x as u16, y as u16), SetColors(Colors::new(fc, bc)), Print(s) ); } fn print_str_def(y: i16, x: i16, s: &str) { print_str(y, x, s, Color::Grey, Color::Reset); } fn print_rect(y: i16, x: i16, h: i16, w: i16, fc: Color, bc: Color) { print_str(y, x, "┌", fc, bc); print_str(y, x + w - 1, "┐", fc, bc); print_str(y + h - 1, x + w - 1, "┘", fc, bc); print_str(y + h - 1, x, "└", fc, bc); for i in 1..(h - 1) { print_str(y + i, x, "│", fc, bc); print_str(y + i, x + w - 1, "│", fc, bc); } for j in 1..(w - 1) { print_str(y, x + j, "─", fc, bc); print_str(y + h - 1, x + j, "─", fc, bc); } } fn print_rect_def(y: i16, x: i16, h: i16, w: i16) { print_rect(y, x, h, w, Color::Grey, Color::Reset); } impl Other { fn new(name: &str, publickey: &str) -> Self { Self { name: String::from(name), publickey: String::from(publickey) } } } impl Realm_CA { fn new(name: &str, secretkey: &str, whitelist_publickeys: & HashSet::, pub_port: u16, height: i16, width: i16, birth: & HashSet::, survival: & HashSet::, autoemit_interval: f64, framerate: i32) -> Self { Self { name: String::from(name), efunguz: Efunguz::new(secretkey, whitelist_publickeys, pub_port, emz::DEF_TOR_PROXY_PORT, emz::DEF_TOR_PROXY_HOST), height, width, cells: (0..height).map(|_| { vec![0u8; width as usize] }).collect::>>(), birth: birth.clone(), survival: survival.clone(), autoemit_interval, framerate, i_turn: 0, cursor_y: height >> 1, cursor_x: width >> 1, others: Vec::new() } } fn add_other(&mut self, name: &str, publickey: &str, onion: &str, port: u16) { if let Ok(eh) = self.efunguz.add_ehypha(publickey, onion, port) { let _ = eh.add_etale(""); let _ = eh.add_etale("zone"); } self.others.push(Other::new(name, publickey)); } fn flip(&mut self, y: Option, x: Option) { let fy = match y { Some(y) => y, None => self.cursor_y }; let fx = match x { Some(x) => x, None => self.cursor_x }; self.cells[fy as usize][fx as usize] ^= 1; } fn clear(&mut self) { for y in 0..self.height { for x in 0..self.width { self.cells[y as usize][x as usize] = 0; } } self.i_turn = 0; } fn reset(&mut self) { let mut rng = thread_rng(); for y in 0..self.height { for x in 0..self.width { self.cells[y as usize][x as usize] = rng.gen::() & 1; } } self.i_turn = 0; } fn render(&self, show_cursor: bool) { let h = self.height; let w = self.width; let w_tert = w / 3; print_rect_def(0, 0, (h >> 1) + 2, w + 2); print_str_def(0, w_tert, "┬┬"); print_str_def(0, w - w_tert, "┬┬"); print_str_def(1 + (h >> 1), w_tert, "┴┴"); print_str_def(1 + (h >> 1), w - w_tert, "┴┴"); print_str_def(0, 2, "[ From others ]"); print_str_def(0, 3 + w - w_tert, "[ To others ]"); let cell_chars = [[" ", "▀"], ["▄", "█"]]; for i in 0..((h >> 1) as usize) { let y = i << 1; let mut row_str = String::new(); for x in 0..(w as usize) { row_str += cell_chars[(self.cells[y + 1][x] & 1) as usize][(self.cells[y][x] & 1) as usize]; } print_str(1 + (i as i16), 1, &row_str, Color::White, Color::Reset); // white on black } let mut status_str: String = format!("[ T = {}", self.i_turn); if show_cursor { let i = (self.cursor_y >> 1) as usize; let m = (self.cursor_y & 1) as usize; let cell_high = (self.cells[i << 1][self.cursor_x as usize] & 1) as usize; let cell_low = (self.cells[(i << 1) + 1][self.cursor_x as usize] & 1) as usize; let chars = [[["▀", "▄"], ["▀", "▀"]], [["▄", "▄"], ["▄", "▀"]]]; let fclrs = [[[Color::DarkRed, Color::DarkRed], [Color::DarkYellow, Color::White]], [[Color::White, Color::DarkYellow], [Color::White, Color::White]]]; let bclrs = [[[Color::Reset, Color::Reset], [Color::Reset, Color::DarkRed]], [[Color::DarkRed, Color::Reset], [Color::DarkYellow, Color::DarkYellow]]]; let s_char = chars[cell_low][cell_high][m]; let s_fclr = fclrs[cell_low][cell_high][m]; let s_bclr = bclrs[cell_low][cell_high][m]; print_str(1 + (i as i16), 1 + self.cursor_x, s_char, s_fclr, s_bclr); status_str += & format!(", X = {}, Y = {}, C = {}", self.cursor_x, self.cursor_y, self.cells[self.cursor_y as usize][self.cursor_x as usize] & 1); } status_str += " ]"; print_str_def(1 + (h >> 1), 1 + ((w - (status_str.len() as i16)) >> 1), &status_str); } fn move_cursor(&mut self, dy: i16, dx: i16) { self.cursor_y = (self.cursor_y + dy).max(0).min(self.height - 1); self.cursor_x = (self.cursor_x + dx).max(0).min(self.width - 1); } fn turn(&mut self) { // Not much optimization... let h = self.height; let w = self.width; // Count alive neighbours for y in 0..h { for x in 0..w { if (self.cells[y as usize][x as usize] & 1) != 0 { // increment number of neighbours for all neighbouring cells for ny in (y - 1)..=(y + 1) { if (ny >= 0) && (ny < h) { for nx in (x - 1)..=(x + 1) { if ((ny != y) || (nx != x)) && (nx >= 0) && (nx < w) { self.cells[ny as usize][nx as usize] += 2; // accumulate in bits 1 and higher } } } } } } } // Update for y in 0..(h as usize) { for x in 0..(w as usize) { let mut c = self.cells[y][x] as usize; if (c & 1) != 0 { c = self.survival.contains(&(c >> 1)) as usize; } else { c = self.birth.contains(&(c >> 1)) as usize; } self.cells[y][x] = c as u8; } } self.i_turn += 1; } fn get_parts_from_zone(&self) -> Vec> { let mut parts = Vec::new(); let h = self.height; let w = self.width; let zh = h; let zw = w / 3; parts.push(zh.to_le_bytes().to_vec()); parts.push(zw.to_le_bytes().to_vec()); parts.push(vec![0u8; (zh * zw) as usize]); for y in 0..(zh as usize) { for x in 0..(zw as usize) { parts[2][y * (zw as usize) + x] = self.cells[y][((w - zw) as usize) + x] & 1; } } parts } fn put_parts_to_zone(&mut self, parts: & Vec>) { if parts.len() == 3 { if (parts[0].len() == 2) && (parts[1].len() == 2) { let szh = i16::from_le_bytes([parts[0][0], parts[0][1]]); let szw = i16::from_le_bytes([parts[1][0], parts[1][1]]); if parts[2].len() == (szh as usize) * (szw as usize) { let dzh = szh.min(self.height); let dzw = szw.min(self.width / 3); for y in 0..(dzh as usize) { for x in 0..(dzw as usize) { self.cells[y][x] = parts[2][y * (szw as usize) + x] & 1; } } } } } } fn emit_etales(&mut self) { self.efunguz.emit_etale("", & vec![ "zone".as_bytes().to_vec(), "2B height (h), 2B width (w), h×wB zone by rows".as_bytes().to_vec() ]); self.efunguz.emit_etale("zone", & self.get_parts_from_zone()); } fn update_efunguz(&mut self) { self.efunguz.update(); } fn run(&mut self) { let (n_rows, _n_columns) = match terminal::size() { Ok((w, h)) => (h as i16, w as i16), _ => (0, 0) }; let h = self.height; // let w = self.width; let mut quit = false; let mut paused = false; let mut render = true; let mut autoemit = true; let t_start = time_musec(); let mut t_last_render: f64 = -65536.0; let mut t_last_emit: f64 = -65536.0; while !quit { let t = 1e-6 * ((time_musec() - t_start) as f64); if (t - t_last_render) * (self.framerate as f64) > 1.0 { // let _ = stdout().queue(Clear(ClearType::All)); // flickering... if render { self.render(paused); } else { print_str_def(0, 0, "Render OFF"); } print_str_def((h >> 1) + 2, 0, & format!("This realm: \"{}'s\" (birth: {:?}, survival: {:?}), SLE {:.1}, autoemit ({:.1}) {}, InAbsorbing {}, InPermitted {}, InAttempted {}", & self.name, & self.birth, & self.survival, t - t_last_emit, self.autoemit_interval, if autoemit {"ON"} else {"OFF"}, self.efunguz.in_absorbing_num(), self.efunguz.in_permitted_num(), self.efunguz.in_attempted_num())); clear_row_right(); print_str_def((h >> 1) + 3, 0, & format!(" since last Disconnect {:.1}, Permit {:.1}, Attempt {:.1}", t - 1e-6 * ((self.efunguz.t_last_disconnect() - t_start) as f64), t - 1e-6 * ((self.efunguz.t_last_permit() - t_start) as f64), t - 1e-6 * ((self.efunguz.t_last_attempt() - t_start) as f64))); clear_row_right(); let mut others_str = String::new(); for i in 0..self.others.len() { if i > 0 { others_str += ", "; } let other = & self.others[i]; others_str += & format!("[{}] \"{}'s\"", i + 1, & other.name); if let Some(eh) = self.efunguz.get_ehypha(& other.publickey) { if let Some(et) = eh.get_etale("zone") { others_str += & format!(" (SLU {:.1})", t - 1e-6 * ((et.t_in() - t_start) as f64)); } } } print_str_def((h >> 1) + 4, 0, & format!("Other realms: {}", &others_str)); clear_row_right(); print_str_def(n_rows - 3, 0, "[Q] quit, [C] clear, [R] reset, [V] render on/off, [P] pause/resume"); clear_row_right(); print_str_def(n_rows - 2, 0, "[A] autoemit on/off, [E] emit, [1-9] import"); clear_row_right(); print_str_def(n_rows - 1, 0, "If paused: [T] turn, [→ ↑ ← ↓] move cursor, [ ] flip cell"); clear_row_right(); let _ = stdout().flush(); t_last_render = t; } if autoemit && (t - t_last_emit > self.autoemit_interval) { self.emit_etales(); t_last_emit = t; } self.update_efunguz(); if !paused { self.turn(); } while let Ok(true) = event::poll(Duration::from_secs(0)) { if let Ok(Event::Key(ke)) = event::read() { if let KeyCode::Char(c) = ke.code { match c { 'q' | 'Q' => { quit = true; }, 'c' | 'C' => { self.clear(); }, 'r' | 'R' => { self.reset(); }, 'v' | 'V' => { let _ = queue!(stdout(), Clear(ClearType::All)); render = !render; }, 'p' | 'P' => { paused = !paused; }, 'a' | 'A' => { autoemit = !autoemit; }, 'e' | 'E' => { self.emit_etales(); t_last_emit = t; }, '1'..='9' => { let i_other = (c as usize) - ('1' as usize); if i_other < self.others.len() { let mut parts: Vec> = Vec::new(); if let Some(eh) = self.efunguz.get_ehypha(& self.others[i_other].publickey) { if let Some(et) = eh.get_etale("zone") { parts = et.parts().clone(); } } self.put_parts_to_zone(&parts); } }, _ => {} } } if paused { match ke.code { KeyCode::Char(c) => { match c { 't' | 'T' => { self.turn(); }, ' ' => { self.flip(None, None); }, _ => {} } }, KeyCode::Right => { self.move_cursor(0, 1); }, KeyCode::Up => { self.move_cursor(-1, 0); }, KeyCode::Left => { self.move_cursor(0, -1); }, KeyCode::Down => { self.move_cursor(1, 0); } _ => {} } } } } } } } fn run_realm(name: &str) -> Result<(), String> { let ( secretkey, pubport, that1_name, that1_publickey, that1_onion, that1_port, that2_name, that2_publickey, that2_onion, that2_port, birth, survival ) = match name.to_ascii_uppercase().as_str() { "ALIEN" => { ( ALIEN_SECRETKEY, ALIEN_PORT, "John", JOHN_PUBLICKEY, JOHN_ONION, JOHN_PORT, "Mary", MARY_PUBLICKEY, MARY_ONION, MARY_PORT, HashSet::::from([3, 4]), HashSet::::from([3, 4]) // 3-4 Life ) }, "JOHN" => { ( JOHN_SECRETKEY, JOHN_PORT, "Alien", ALIEN_PUBLICKEY, ALIEN_ONION, ALIEN_PORT, "Mary", MARY_PUBLICKEY, MARY_ONION, MARY_PORT, HashSet::::from([3]), HashSet::::from([2, 3]) // classic Conway's Life ) }, "MARY" => { ( MARY_SECRETKEY, MARY_PORT, "Alien", ALIEN_PUBLICKEY, ALIEN_ONION, ALIEN_PORT, "John", JOHN_PUBLICKEY, JOHN_ONION, JOHN_PORT, HashSet::::from([3]), HashSet::::from([2, 3]) // classic Conway's Life ) }, _ => { return Err(format!("Unknown realm name: \"{}\". Must be \"Alien\", \"John\", or \"Mary\".", name)) } }; let (height, width) = match terminal::size() { Ok((w, h)) => {( ((h as i16) - 9) << 1, // even (w as i16) - 2 )}, _ => { return Err(String::from("Cannot obtain terminal size")); } }; let mut realm = Realm_CA::new(name, secretkey, & HashSet::new(), pubport, height, width, &birth, &survival, DEF_AUTOEMIT_INTERVAL, DEF_FRAMERATE); // Uncomment to restrict: Alien gets data from John and Mary; John gets data from Alien but not from Mary; Mary gets data from neither Alien, nor John // realm.efunguz.add_whitelist_publickeys(& HashSet::from([String::from(that1_publickey)])); realm.add_other(that1_name, that1_publickey, that1_onion, that1_port); realm.add_other(that2_name, that2_publickey, that2_onion, that2_port); realm.reset(); init_term_graphics()?; realm.run(); drop_term_graphics()?; Ok(()) } fn main() { let args = env::args().collect::>(); if args.len() >= 2 { match run_realm(& args[1]) { Ok(_) => {}, Err(s) => { println!("Error: {}", s); } } } else { println!("Syntax:"); println!("demo "); } }