main.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. use std::ops::{Index,IndexMut};
  2. use rand::prelude::random;
  3. // it's premature abstraction to make this a trait, but whatever!
  4. trait Pixel: Copy + Clone {
  5. // if we make an image with these as pixels, this'll be the
  6. // default background
  7. fn empty() -> Self;
  8. // how many color stops do we want to report in the PPM file?
  9. fn depth() -> usize;
  10. // print this. (hooboy, this ain't efficient, but eh: fast enough
  11. // for the one-off script this is! if I turn this into a library,
  12. // I'd make this take a formatter instead)
  13. fn show(&self) -> String;
  14. }
  15. // I left this in after testing this for a simple grayscale version
  16. impl Pixel for bool {
  17. // defaults to white
  18. fn empty() -> bool { false }
  19. fn depth() -> usize { 1 }
  20. fn show(&self) -> String {
  21. if *self {
  22. "1 1 1"
  23. } else {
  24. "0 0 0"
  25. }.to_string()
  26. }
  27. }
  28. // The three kinds of pixels: Black, White, and Red
  29. #[derive(Copy, Clone)]
  30. enum Px {
  31. Black,
  32. White,
  33. Red
  34. }
  35. impl Px {
  36. // we use this in the CA impl, later on
  37. fn idx(&self) -> usize {
  38. match self {
  39. Px::Black => 0,
  40. Px::White => 1,
  41. Px::Red => 2,
  42. }
  43. }
  44. }
  45. impl Pixel for Px {
  46. fn empty() -> Px { Px::White }
  47. fn depth() -> usize { 1 }
  48. fn show(&self) -> String {
  49. match self {
  50. Px::Black => "0 0 0",
  51. Px::White => "1 1 1",
  52. Px::Red => "1 0 0",
  53. }.to_string()
  54. }
  55. }
  56. // Our simple image abstraction
  57. struct Image<T> {
  58. width: usize,
  59. height: usize,
  60. // we maintain the invariant that the length of `data` here is
  61. // `width * height`. (Or at least, if we don't, things
  62. // crash. Fun!)
  63. data: Vec<T>,
  64. }
  65. impl<T: Pixel> Image<T> {
  66. fn new(width: usize, height: usize) -> Image<T> {
  67. let data = vec![T::empty(); width*height];
  68. Image {
  69. width,
  70. height,
  71. data,
  72. }
  73. }
  74. // This prints the PPM file:
  75. fn show(&self) -> String {
  76. let mut str = String::new();
  77. str.push_str("P3\n");
  78. str.push_str(&format!("{} {}\n", self.width, self.height));
  79. str.push_str(&format!("{}\n", T::depth()));
  80. for px in self.data.iter() {
  81. str.push_str(&format!("{} ", px.show()));
  82. }
  83. str
  84. }
  85. // This looks up the pixel, but returns an 'empty' pixel if we
  86. // can't find it.
  87. fn get(&self, (x, y): (usize, usize)) -> T {
  88. // ...I only just realized while commenting this file that
  89. // this is wrong, but I'm too lazy to fix it now.
  90. *self.data.get(x + y * self.height).unwrap_or(&T::empty())
  91. }
  92. }
  93. // This lets us index into our image using a tuple as coordinate!
  94. impl<T: Pixel> Index<(usize, usize)> for Image<T> {
  95. type Output = T;
  96. fn index(&self, (x, y): (usize, usize)) -> &T {
  97. &self.data[x + y * self.height]
  98. }
  99. }
  100. // This lets us modify our image too!
  101. impl <T: Pixel> IndexMut<(usize, usize)> for Image<T> {
  102. fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut T {
  103. &mut self.data[x + y * self.height]
  104. }
  105. }
  106. // Okay, here's where the CA stuff comes in. So: a given 'generation'
  107. // in this system is a vector of cells, where each cell is either
  108. // black, white, or red. Each subsequent generation, a cell turns into
  109. // a new cell based on a rule which applies to the previous
  110. // generation, looking at the same cell and its immediate
  111. // neighbors. With `n` possible states, that gives us `n**3` possible
  112. // 'neighborhoods': in this case, 27. So we can describe an automaton
  113. // of this form by simply enumerating the resut for each of the
  114. // neighborhoods, which is why this has 27 different `Px` values: one
  115. // for each possible neighborhood.
  116. struct Rule {
  117. result: [Px;27],
  118. }
  119. impl Rule {
  120. // We can describe a given automaton by using a string of 27
  121. // characters. We use this to create the filenames, so we could in
  122. // theory reproduce the automaton again later on
  123. fn descr(&self) -> String {
  124. let mut str = String::new();
  125. for r in self.result.iter() {
  126. str.push(match r {
  127. Px::White => 'w',
  128. Px::Black => 'b',
  129. Px::Red => 'r',
  130. })
  131. }
  132. str
  133. }
  134. // This implements the logic (which is really just the lookup) for
  135. // 'how do we know the cell at generation n given the neighboor at
  136. // generation n-1?'
  137. fn step(&self, (l, c, r): (Px, Px, Px)) -> Px {
  138. let index = l.idx() + c.idx() * 3 + r.idx() * 9;
  139. self.result[index]
  140. }
  141. // This generates a random automaton.
  142. fn random() -> Rule {
  143. let mut result = [Px::White; 27];
  144. for i in 0..27 {
  145. let r: f32 = random();
  146. result[i] = if r < 0.33 {
  147. Px::White
  148. } else if r > 0.66 {
  149. Px::Black
  150. } else {
  151. Px::Red
  152. }
  153. }
  154. Rule { result }
  155. }
  156. }
  157. fn main() {
  158. // I choose something odd so our initial condition can be all
  159. // white cells with a single black cell in the middle to make
  160. // something interesting happen
  161. let w = 99;
  162. let mut img: Image<Px> = Image::new(w, w);
  163. img[(w/2, 0)] = Px::Black;
  164. // choose a random rule and find out what it is
  165. let rule = Rule::random();
  166. eprintln!("Using {}", rule.descr());
  167. // for each generation (except the last)
  168. for y in 0..(w-1) {
  169. // this is the logic on the left-hand side, where we hard-code
  170. // that the stuff off the side is the 'empty' value.
  171. img[(0, y+1)] = rule.step((Px::empty(), img[(0, y)], img[(1, y)]));
  172. // for everything in the middle, we calculate the neighborhood and then step the rule
  173. for x in 1..(w-1) {
  174. let env = (img.get((x, y)), img.get((x+1, y)), img.get((x+2, y)));
  175. img[(x+1, y+1)] = rule.step(env);
  176. }
  177. // ditto for the right-hand side
  178. img[(w-1, y+1)] = rule.step((img[(w-2, y)], img[(w-1, y)], Px::empty()));
  179. }
  180. // print this out
  181. {
  182. std::fs::write(
  183. &format!("output/{}.ppm", rule.descr()),
  184. img.show().as_bytes(),
  185. ).unwrap();
  186. }
  187. }