The earliest memory I have of ‘programming’ is in the early/mid 90s when my father brought home a computer from work. We could play games on it … so of course I took the spreadsheet program he used (LOTUS 123, did I date myself with that?) and tried to modify it to print out a helpful message for him. It … halfway worked? At least I could undo it so he could get back to work…

After that, I picked up programming for real in QBASIC (I still have a few of those programs lying around), got my own (junky) Linux desktop from my cousin, tried to learn VBasic (without a Windows machine), and eventually made it to high school… In college, I studied computer science and mathematics, mostly programming in Java/.NET, although with a bit of everything in the mix. A few of my oldest programming posts on this blog are from that time.

After that, on to grad school! Originally, I was going to study computational linguistics, but that fell through. Then programming languages (the school’s specialty). And finally I ended up studying censorship and computer security. That’s about where I am today!

But really, I still have a habit of doing a little bit of everything. Whatever seems interesting at the time!

Genuary 2023.14: Asemic Writing

Genuary!

Spend a month making one beautiful thing per day, given a bunch of prompts. A month late, but as they say, ’the second best time is now'.

Let’s do it!

14) Asemic writing

Wikipedia: Asemic writing

Use the same Wikipedia code as yesterday to get a random page, render each word as a line that meanders based on the letters of the word. Shiny. 😄

read more...


Genuary 2023.13: Something you've always wanted to learn

Genuary!

Spend a month making one beautiful thing per day, given a bunch of prompts. A month late, but as they say, ’the second best time is now'.

Let’s do it!

13) Something you’ve always wanted to learn

META! LEARN EVERYTHING!

Fetch a random page from Wikipedia and scroll it by super quick, see how fast you can speed read it. And heck, you might just learn something. I know I did!


let url;
let titleToRender;
let wordsToRender;
let wordIndex;

let nextButton;
let wikiButton;

const CHAR_SIZE = 60;

function setup() {
  createCanvas(400, 400);
  frameRate(10);
  
  nextButton = createButton("next");
  nextButton.mousePressed(renderRandomPage);
  
  wikiButton = createButton("open");
  wikiButton.mousePressed(() => {
    window.open(url, '_blank');
  });
  
  wordsToRender = [];
  wordIndex = 0;
  
  renderRandomPage();
}

function draw() {
  if (wordIndex > wordsToRender.length) {
    noLoop();
  }
  
  clear();
  textSize(CHAR_SIZE);
  for (let i = 0; i < height / CHAR_SIZE; i++) {
    if (i == 3) {
      fill("black");
    } else {
      fill("lightgray");
    }
    
    text(
      wordsToRender[wordIndex + i],
      10,
      CHAR_SIZE * (i + 1)
    );
  }
  
  stroke("black");
  line(
    width - 10, 
    10, 
    width - 10, 
    height - 10
  );
  circle(
    width - 10,
    1.0 * wordIndex / wordsToRender.length * (height - 20),
    20
  );
  
  wordIndex++;
}

function renderRandomPage() {
  wikiButton.attribute('disabled', '');
  
  // https://stackoverflow.com/a/70225116
  async function go() {
    let title;
    {
      let json = await httpGet(`https://en.wikipedia.org/w/api.php?action=query&format=json&generator=random&grnlimit=1&grnnamespace=0&prop=info&origin=*`, 'json');
      let pages = json.query.pages;
      let id = Object.keys(pages)[0];
      title = pages[id].title;
      url = `https://en.wikipedia.org/wiki/${title}`;
    }
    
    let body;
    {
      let json = await httpGet(`https://en.wikipedia.org/w/api.php?action=query&format=json&titles=${title}&prop=extracts&explaintext&origin=*`, 'json');
      let pages = json.query.pages;
      let id = Object.keys(pages)[0];
      body = pages[id].extract;
    }
    
    return [title, body];
  }
  
  background(255);
  text("Loading...", 10, 20);
  
  go().then(([title, body]) => {
    titleToRender = title;
    wordsToRender = body.trim().split(/\s+/);
    wordIndex = 0;
    wikiButton.removeAttribute('disabled');
    loop();
  });
}

read more...


Genuary 2023.12: Tessellation

Genuary!

Spend a month making one beautiful thing per day, given a bunch of prompts. A month late, but as they say, ’the second best time is now'.

Let’s do it!

12) Tessellation

Wikipedia: Tessellation (List)

This was not at all the direction I ended to go, but it got really interesting, so I decided to keep it.

What I remember doing years and years ago, I think roughly around the time of Windows 95 (I feel old) or perhaps even pre-Windows was using a tesselation program that would give you a basic shape and then allow you to pull the edges in a way it would propagate to automatically keep the tesselation valid. It was pretty awesome… but I have no idea what it was called any more.

It might have been Shodor on Tessellations.org, or perhaps TesselMania!. Been a while.

In any case, enjoy!


const SIZE = 40;

let gui;
let params = {
  scale: 1.0, scaleMin: 0.25, scaleMax: 3.0, scaleStep: 0.01,
  drawBorders: true,
  applyRotation: true,
  fastFlux: true,
  varyRotations: true,
}

let g;
let buffer;
let bufferMask;
let lastRedrawBuffer;

function setup() {
  createCanvas(400, 400);
  g = createGraphics(width, height);
  
  buffer = createGraphics(2 * SIZE, 2 * SIZE);
  for (let i = 0; i < 100; i++) {
    buffer.fill(
      255 * random(),
      255 * random(),
      255 * random(),
      255 * random()
    );
    buffer.rect(
      random(4 * SIZE) - 2 * SIZE,
      random(4 * SIZE) - 2 * SIZE, 
      random(2 * SIZE),
      random(2 * SIZE)
    )
  }
  redrawBuffer();
  
  bufferMask = createGraphics(2 * SIZE, 2 * SIZE);
  bufferMask.push();
  {
    bufferMask.translate(SIZE, SIZE);
    bufferMask.scale(params.scale);
    bufferMask.stroke("black");
    bufferMask.fill("black");
    ngon(bufferMask, 6, SIZE);
  }
  bufferMask.pop();
 
  gui = createGuiPanel('params');
  gui.addObject(params);
  gui.setPosition(420, 0);
}

function draw() {
  g.background(255);
  
  if (params.fastFlux) {
    drawOneToBuffer();
  } else {
    if (millis() - lastRedrawBuffer > 1000) {
      redrawBuffer();
    }
  }
  
  let masked = buffer.get();
  masked.mask(bufferMask);
  
  g.push(); 
  {
    g.stroke("black");
    g.fill("green");
  
    g.translate(200, 200);
    g.scale(params.scale);
    
    g.stroke("black");
    g.noFill();
    
    if (params.drawBorders) {
      g.stroke("black");
      g.strokeWeight(2);
      ngon(g, 6, SIZE);      
    }
    
    g.push();
    {
      g.translate(-SIZE, -SIZE);
      g.image(masked, 0, 0);
    }
    g.pop();
        
    for (let xd = -10; xd < 10; xd++) {
      for (let yd = -50; yd < 50; yd++) {
        if (xd == 0 && yd == 0) continue;
        
        g.push();
        {
          let rowOffset = abs(yd) % 2 == 1 ? 1.5 * SIZE : 0;
          
          // Major thanks to:
          // https://www.redblobgames.com/grids/hexagons/
          g.translate(
            rowOffset + 3.0 * xd * SIZE, 
            sqrt(3) / 2 * yd * SIZE
          );
          
          if (params.applyRotation) {
            let n = noise(
                xd,
                yd,
                params.varyRotations ? frameCount / 500.0 : 0
            );
            g.rotate(TWO_PI / 6.0 * parseInt(n * 6));
          }
          
          g.push();
          {
            g.translate(-SIZE, -SIZE);
            g.image(masked, 0, 0);
          }
          g.pop();
          
          if (params.drawBorders) {
            g.stroke("black");
            g.strokeWeight(2);
            ngon(g, 6, SIZE);            
          }
        }
        g.pop();
      }
    }
  }
  g.pop();
  
  image(g, 0, 0);
}

function ngon(g, n, size) {
  g.beginShape();
  for (let i = 0; i < n; i++) {
    let x = size * cos(TWO_PI * i / n);
    let y = size * sin(TWO_PI * i / n);
    g.vertex(x, y);
  }
  g.endShape(CLOSE);
}

function redrawBuffer() {
  buffer.background(255);
  for (let i = 0; i < 100; i++) {
    drawOneToBuffer();
  }
  lastRedrawBuffer = millis();
}

function drawOneToBuffer() {
  buffer.fill(
    255 * random(),
    255 * random(),
    255 * random(),
    255 * random()
  );
  buffer.rect(
    random(4 * SIZE) - 2 * SIZE,
    random(4 * SIZE) - 2 * SIZE, 
    random(2 * SIZE),
    random(2 * SIZE)
  );
}

read more...


Genuary 2023.10: Generative Music

Genuary!

Spend a month making one beautiful thing per day, given a bunch of prompts. A month late, but as they say, ’the second best time is now'.

Let’s do it!

10) Generative music

Wikipedia: Generative music

NOTE: Because of limitations in JavaScript autoplaying sound, you must click to start.

NOTE: This currently doesn’t work in Safari. Something funny with the p5.sound addon. I’m working on it.

read more...