Browse Source

Add the other examples that do not appear in the source, too

Getty Ritter 3 years ago
parent
commit
e5c5a4143c

File diff suppressed because it is too large
+ 4 - 2
in-praise-of-pbm/POST.md


+ 80 - 0
in-praise-of-pbm/examples/glyphs/main.rb

@@ -0,0 +1,80 @@
+class Image
+  attr_reader :width, :height
+
+  def initialize(width:, height:)
+    @width = width
+    @height = height
+    @data = Array.new(width * height, 1)
+  end
+
+  def [](x, y)
+    @data[x + y * @height]
+  end
+
+  def []=(x, y, r)
+    @data[x + y * @height] = r
+  end
+
+  def scale(factor)
+    img = Image.new(width: @width * factor, height: @height * factor)
+    (0...@width).each do |x|
+      (0...@height).each do |y|
+        (0...factor).each do |dx|
+          (0...factor).each do |dy|
+            img[x*factor+dx,y*factor+dy] = self[x,y]
+          end
+        end
+      end
+    end
+    img
+  end
+
+  def blit(img, at_x, at_y)
+    (0...img.width).each do |x|
+      (0...img.height).each do |y|
+        self[at_x + x, at_y + y] = img[x, y]
+      end
+    end
+  end
+
+  def to_pbm
+    buf = String.new
+    buf << "P1\n"
+    buf << "#{@width} #{@height}\n"
+    @data.each {|x| buf << "#{x} "}
+    buf
+  end
+
+  def self.glyph(size)
+    img = Image.new(width: size, height: size)
+    (0...size).each do |x|
+      (0...size).each do |y|
+        if x % 2 == 0 && y % 2 == 0 then
+          img[x, y] = 0
+        elsif (x % 2 == 0 || y % 2 == 0) && rand > 0.5 then
+          img[x, y] = 0
+        end
+      end
+    end
+    img
+  end
+end
+
+module Main
+  def self.main
+    glyphs_w = 4
+    glyphs_h = 4
+    size = 5
+    img = Image.new(width: glyphs_w * (size + 2) + 2, height: glyphs_h * (size + 2) + 2)
+    (0...glyphs_w).each do |x|
+      (0...glyphs_h).each do |y|
+        img.blit(Image.glyph(size), 2 + x * (size + 2), 2 + y * (size + 2))
+      end
+    end
+    puts img.scale(20).to_pbm
+  end
+end
+
+if $PROGRAM_NAME == __FILE__ then
+  Main.main
+end

+ 34 - 0
in-praise-of-pbm/examples/mazelike/mazelike.py

@@ -0,0 +1,34 @@
+import random
+
+# we rely on these being odd later on
+WIDTH, HEIGHT = 51, 31
+
+# set up all the pixels as black for the time being
+PIXELS = {(x,y): 1 for x in range(WIDTH) for y in range(HEIGHT)}
+
+# for every pixel in the image
+for (x, y) in PIXELS:
+    # this effectively makes a grid of white pixels separated by black
+    # lines
+    if x % 2 == 1 and y % 2 == 1:
+        PIXELS[(x, y)] = 0
+        # but at each grid line, choose to make a random line going
+        # either right or down
+        if random.random() > 0.5:
+            PIXELS[(x+1, y)] = 0
+        else:
+            PIXELS[(x, y+1)] = 0
+
+    # also just make the edges black so we don't have white trails
+    # going off the side.
+    if x == WIDTH - 1 or y == HEIGHT - 1:
+        PIXELS[(x, y)] = 1
+
+# print the PBM file
+print('P1')
+print(f'{WIDTH} {HEIGHT}')
+for y in range(HEIGHT):
+    # we could do this all on one line, but this is nice for debugging
+    for x in range(WIDTH):
+        print(f'{PIXELS[x,y]}', end=' ')
+    print()

+ 1 - 0
in-praise-of-pbm/examples/threecell/.gitignore

@@ -0,0 +1 @@
+/target

+ 85 - 0
in-praise-of-pbm/examples/threecell/Cargo.lock

@@ -0,0 +1,85 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "threecell"
+version = "0.1.0"
+dependencies = [
+ "rand",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"

+ 10 - 0
in-praise-of-pbm/examples/threecell/Cargo.toml

@@ -0,0 +1,10 @@
+[package]
+name = "threecell"
+version = "0.1.0"
+authors = ["Getty Ritter <tristero@infinitenegativeutility.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+rand = "*"

+ 208 - 0
in-praise-of-pbm/examples/threecell/src/main.rs

@@ -0,0 +1,208 @@
+use std::ops::{Index,IndexMut};
+use rand::prelude::random;
+
+// it's premature abstraction to make this a trait, but whatever!
+trait Pixel: Copy + Clone {
+    // if we make an image with these as pixels, this'll be the
+    // default background
+    fn empty() -> Self;
+    // how many color stops do we want to report in the PPM file?
+    fn depth() -> usize;
+    // print this. (hooboy, this ain't efficient, but eh: fast enough
+    // for the one-off script this is! if I turn this into a library,
+    // I'd make this take a formatter instead)
+    fn show(&self) -> String;
+}
+
+// I left this in after testing this for a simple grayscale version
+impl Pixel for bool {
+    // defaults to white
+    fn empty() -> bool { false }
+    fn depth() -> usize { 1 }
+    fn show(&self) -> String {
+        if *self {
+            "1 1 1"
+        } else {
+            "0 0 0"
+        }.to_string()
+    }
+}
+
+// The three kinds of pixels: Black, White, and Red
+#[derive(Copy, Clone)]
+enum Px {
+    Black,
+    White,
+    Red
+}
+
+impl Px {
+    // we use this in the CA impl, later on
+    fn idx(&self) -> usize {
+        match self {
+            Px::Black => 0,
+            Px::White => 1,
+            Px::Red => 2,
+        }
+    }
+}
+
+impl Pixel for Px {
+    fn empty() -> Px { Px::White }
+    fn depth() -> usize { 1 }
+    fn show(&self) -> String {
+        match self {
+            Px::Black => "0 0 0",
+            Px::White => "1 1 1",
+            Px::Red => "1 0 0",
+        }.to_string()
+    }
+}
+
+// Our simple image abstraction
+struct Image<T> {
+    width: usize,
+    height: usize,
+    // we maintain the invariant that the length of `data` here is
+    // `width * height`. (Or at least, if we don't, things
+    // crash. Fun!)
+    data: Vec<T>,
+}
+
+impl<T: Pixel> Image<T> {
+    fn new(width: usize, height: usize) -> Image<T> {
+        let data = vec![T::empty(); width*height];
+        Image {
+            width,
+            height,
+            data,
+        }
+    }
+
+    // This prints the PPM file:
+    fn show(&self) -> String {
+        let mut str = String::new();
+        str.push_str("P3\n");
+        str.push_str(&format!("{} {}\n", self.width, self.height));
+        str.push_str(&format!("{}\n", T::depth()));
+        for px in self.data.iter() {
+            str.push_str(&format!("{} ", px.show()));
+        }
+        str
+    }
+
+    // This looks up the pixel, but returns an 'empty' pixel if we
+    // can't find it.
+    fn get(&self, (x, y): (usize, usize)) -> T {
+        // ...I only just realized while commenting this file that
+        // this is wrong, but I'm too lazy to fix it now.
+        *self.data.get(x + y * self.height).unwrap_or(&T::empty())
+    }
+}
+
+// This lets us index into our image using a tuple as coordinate!
+impl<T: Pixel> Index<(usize, usize)> for Image<T> {
+    type Output = T;
+
+    fn index(&self, (x, y): (usize, usize)) -> &T {
+        &self.data[x + y * self.height]
+    }
+}
+
+// This lets us modify our image too!
+impl <T: Pixel> IndexMut<(usize, usize)> for Image<T> {
+    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut T {
+        &mut self.data[x + y * self.height]
+    }
+}
+
+
+// Okay, here's where the CA stuff comes in. So: a given 'generation'
+// in this system is a vector of cells, where each cell is either
+// black, white, or red. Each subsequent generation, a cell turns into
+// a new cell based on a rule which applies to the previous
+// generation, looking at the same cell and its immediate
+// neighbors. With `n` possible states, that gives us `n**3` possible
+// 'neighborhoods': in this case, 27. So we can describe an automaton
+// of this form by simply enumerating the resut for each of the
+// neighborhoods, which is why this has 27 different `Px` values: one
+// for each possible neighborhood.
+struct Rule {
+    result: [Px;27],
+}
+
+impl Rule {
+    // We can describe a given automaton by using a string of 27
+    // characters. We use this to create the filenames, so we could in
+    // theory reproduce the automaton again later on
+    fn descr(&self) -> String {
+        let mut str = String::new();
+        for r in self.result.iter() {
+            str.push(match r {
+                Px::White => 'w',
+                Px::Black => 'b',
+                Px::Red => 'r',
+            })
+        }
+        str
+    }
+
+    // This implements the logic (which is really just the lookup) for
+    // 'how do we know the cell at generation n given the neighboor at
+    // generation n-1?'
+    fn step(&self, (l, c, r): (Px, Px, Px)) -> Px {
+        let index = l.idx() + c.idx() * 3 + r.idx() * 9;
+        self.result[index]
+    }
+
+    // This generates a random automaton.
+    fn random() -> Rule {
+        let mut result = [Px::White; 27];
+        for i in 0..27 {
+            let r: f32 = random();
+            result[i] = if r < 0.33 {
+                Px::White
+            } else if r > 0.66 {
+                Px::Black
+            } else {
+                Px::Red
+            }
+        }
+        Rule { result }
+    }
+}
+
+fn main() {
+    // I choose something odd so our initial condition can be all
+    // white cells with a single black cell in the middle to make
+    // something interesting happen
+    let w = 99;
+    let mut img: Image<Px> = Image::new(w, w);
+    img[(w/2, 0)] = Px::Black;
+
+    // choose a random rule and find out what it is
+    let rule = Rule::random();
+    eprintln!("Using {}", rule.descr());
+
+    // for each generation (except the last)
+    for y in 0..(w-1) {
+        // this is the logic on the left-hand side, where we hard-code
+        // that the stuff off the side is the 'empty' value.
+        img[(0, y+1)] = rule.step((Px::empty(), img[(0, y)], img[(1, y)]));
+        // for everything in the middle, we calculate the neighborhood and then step the rule
+        for x in 1..(w-1) {
+            let env = (img.get((x, y)), img.get((x+1, y)), img.get((x+2, y)));
+            img[(x+1, y+1)] = rule.step(env);
+        }
+        // ditto for the right-hand side
+        img[(w-1, y+1)] = rule.step((img[(w-2, y)], img[(w-1, y)], Px::empty()));
+    }
+
+    // print this out
+    {
+        std::fs::write(
+            &format!("output/{}.ppm", rule.descr()),
+            img.show().as_bytes(),
+        ).unwrap();
+    }
+}

BIN
in-praise-of-pbm/images/glyphs.png


BIN
in-praise-of-pbm/images/mazelike.png