organicomplex 0.7.0

Interactive complex-valued cellular automaton on 2D and 3D grids in search of that stuff - emergence, open-endedness, organicity etc.
/*
 * OrganiComplex
 *
 * Interactive complex-valued cellular automaton on 2D and 3D grids in search
 * of that stuff - emergence, open-endedness, organicity etc.
 * 
 * https://sunkware.org/organicomplex
 * 
 * mediator@sunkware.org
 * 
 * Copyright (c) 2026 Sunkware
 * 
 * OrganiComplex 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.
 *
 * OrganiComplex 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 OrganiComplex. If not, see <https://www.gnu.org/licenses/>.
 */

use std::{
    collections::HashSet,
    fs::File,
    io::{
        BufReader,
        BufWriter,
        Read,
        Write
    }
};

use crate::base::{
    ReadBin,
    WriteBin
};

const DEF_U_RADIUS: isize = 0;
const DEF_V_RADIUS: isize = 2;
const DEF_W_RADIUS: isize = 2;

/*
// Hexagon cell with 6 adjacent cells
pub const NEIGHBOURS: [(isize, isize); 18] = [
    // (0isize, 0isize),
    (-1, 0), (-1, -1), (0, -1), (1, 0), (1, 1), (0, 1),
    (-1, 1), (-2, 0), (-2, -1), (-2, -2), (-1, -2), (0, -2), (1, -1), (2, 0), (2, 1), (2, 2), (1, 2), (0, 2),
    // (-1, 2), (-2, 1), (-3, 0), (-3, -1), (-3, -2), (-3, -3), (-2, -3), (-1, -3), (0, -3), (1, -2), (2, -1), (3, 0), (3, 1), (3, 2), (3, 3), (2, 3), (1, 3), (0, 3)
];
*/

pub struct Neighbourhood (pub HashSet<(isize, isize, isize)>);

impl<W: Write> WriteBin<&Neighbourhood> for W {
    fn write_bin(&mut self, nbhood: &Neighbourhood) -> Result<(), String> {
        self.write_bin(nbhood.0.len())?;
        for &(du, dv, dw) in & nbhood.0 {
            self.write_bin(du as i16)?;
            self.write_bin(dv as i16)?;
            self.write_bin(dw as i16)?;
        }
        Ok(())
    }
}

impl<R: Read> ReadBin<Neighbourhood> for R {
    fn read_bin(&mut self) -> Result<Neighbourhood, String> {
        let mut nbhood = HashSet::<(isize, isize, isize)>::new();
        let n: usize = self.read_bin()?;
        for _ in 0..n {
            let du: i16 = self.read_bin()?;
            let dv: i16 = self.read_bin()?;
            let dw: i16 = self.read_bin()?;
            nbhood.insert((du as isize, dv as isize, dw as isize));
        }
        Ok(Neighbourhood(nbhood))
    }
}

impl Neighbourhood {
    pub fn new_default() -> Self {
        let mut neighbourhood = HashSet::<(isize, isize, isize)>::new();
        for du in -DEF_U_RADIUS..=DEF_U_RADIUS {
            for dv in -DEF_V_RADIUS..=DEF_V_RADIUS {
                for dw in -DEF_W_RADIUS..=DEF_W_RADIUS {
                    neighbourhood.insert((du, dv, dw));                   
                }
            }
        }
        Self(neighbourhood)
    }

    pub fn reset(&mut self) {
        *self = Self::new_default();
    }

    pub fn is_symmetric(&self) -> bool {
        for &(du, dv, dw) in & self.0 {
            if ! self.0.contains(&(-du, -dv, -dw)) {
                return false;
            }
        }
        true
    }

    // Multiplier M for normalising function that preserves constant field filled with M/4
    pub fn perpetuator(&self) -> f64 {
        let neigh_len = self.0.len();
        if neigh_len > 0 {
            let neigh_len = neigh_len as f64;
            2.0 / neigh_len * ((1.0 + 2.0 * neigh_len).sqrt() - 1.0) // depends on normalising function
        } else {
            2.0
        }
    }

    pub fn load(filepath: &str) -> Result<Self, String> {
        // "Binary deserialization"... assumes that structures are fixed. Cf. save()
        let mut reader = BufReader::new(File::open(filepath).map_err(|e| e.to_string())?);
        reader.read_bin()
    }

    pub fn save(&self, filepath: &str) -> Result<(), String> {
        // "Binary serialization"... assumes that structures are fixed. Cf. load()
        let mut writer = BufWriter::new(File::create(filepath).map_err(|e| e.to_string())?);
        writer.write_bin(self)?;
        writer.flush().map_err(|e| e.to_string())?;
        Ok(())
    }
}