conye – asemic

Here are pictures of my asemic writing! Every phrase input through my program will be encoded into a solvable maze.

I imagine the paper below coming from an ancient society of space creatures who think in riddles and mazes.

The text above: “PSA: The correct way to cut bread is diagonally”

Then, the below picture is still the same language, but after the space creatures have evolved over centuries into phone reliant creatures like us.

The text above: “are you interested in making dumplings with me” “nah”

For this language, I imagine some space creatures speaking in riddles.

Here are some gifs of it being executed!

The process:

Some first sketches!

First I created an alphabet of maze pieces. Each of the symbols depicted below corresponds to a letter of an alphabet, in order (left to right, top to bottom, with “spacebar” symbol as the 27th symbol). For the most part, I tried to make the symbols vaguely resemble their counterparts. For example, I think “b”, “d”, c”, “o”, “u”, “t”, “y” and “x” are decently similar, but the rest are only tangentially related.

If we just stick these maze bits together, we’ll end up with an unsolvable maze (this is a slightly earlier and different version of the alphabet):

So, I spent a lot of time thinking about how I should make the maze solvable. At first, I considered adding pieces, and rotating them until they fit. If they didn’t fit, I could keep a stack of states, so I would take one step back and rotate the previous piece until I could fit in the new one. However, I figured that rotating the pieces made the language even more unreadable than it already was, and also even if I rotated them, there was still no guarantee that we could even end up with a maze.

So, I ultimately decided to carve a path through the pieces after I had placed them. I used a variation of the union-find algorithm that I learned last semester in 15-122. For every alphabet piece in the 2d array, I would give it a unique id. Then, every time I placed a new piece down, I would check to see if it was already connected to the piece on the left or the piece above it. If yes, this new piece would have the old piece’s id. In the unique case that it was connected both above and to the left, I would give the new piece the id of its upper neighbor, then recursively reassign the left piece and all of its neighbors’ ids to be that of the upper piece’s id. Therefore, by the end, every connected segment of the maze had the same id. I then generated a random subset of possible connections between neighbors and only carved paths between two maze bits if they had different ids.

Here’s an example of a maze that isn’t fully connected, but all of the connected maze pieces had the same id.

Here’s after the path carving algorithm runs!

Final thoughts:

I really enjoyed this project. I thought it was awesome to be able to hold a final result in my hands, and it was super refreshing to see my work move from the screen and onto paper. I also really enjoyed working with the transparent sheet because it felt like the stylistic possibilities were endless.

Here are some more patterns that I generated!

The text above (if I remember correctly): “This chicken is so undercooked a skilled vet could still save him”

The text above (if I remember correctly): “I strongly dislike moldy soup it is so terrifying”

My code:

import processing.pdf.*;
 
int pieceWidth = 4;
int pieceHeight = 7;
int wid = 9;
int hei = 3;
int step = 15; //width of a single "box"
int strlen;
MazePiece[][] pieces;
boolean bRecordingPDF;
int pdfOutputCount = 0; 
 
void keyPressed() {
  // When you press a key, it will initiate a PDF export
  bRecordingPDF = true;
}
 
 
class MazePiece{
  public boolean[][] edges;
  public MazePiece upNeighbor;
  public MazePiece downNeighbor;
  public MazePiece leftNeighbor;
  public MazePiece rightNeighbor;
  public String id;
 
  public MazePiece(boolean[][] b, String i){
    edges = b;
    id = i;
  }
}
 
// below is the alphabet
boolean[][] a = new boolean[][]{{true, true, true, false},{true, false, false, true},
  {false, true, false, false},{true, true, true, true},{false, false, false, false},
  {true, false, true, true},{true, true, false, false}};
boolean[][] b = new boolean[][]{{false, false, false, false},{true, false, false, false},
  {false, true, true, false},{true, true, false, true},{false, true, false, false},
  {true, false, false, true},{true, true, true, false}};
boolean[][] c = new boolean[][]{{true, true, true, false},{true, false, false, true},
  {false, true, true, false},{true, false, false, false},{false, true, true, false},
  {true, false, false, true},{true, true, true, false}};
boolean[][] d = new boolean[][]{{false, false, false, false},{false, false, false, true},
  {true, true, false, false},{true, false, true, true},{false, true, false, false},
  {true, false, false, true},{true, true, true, false}};
boolean[][] e = new boolean[][]{{true, true, true, false},{true, false, false, true},
  {false, true, true, false},{true, false, false, false},{false, false, false, false},
  {true, true, true, true},{true, false, true, false}};
boolean[][] f = new boolean[][]{{true, true, false, false},{true, false, true, false},
  {false, false, false, false},{true, true, false, false},{true, false, true, false},
  {true, false, true, false},{false, false, false, false}};
boolean[][] g = new boolean[][]{{true, true, false, false},{true, false, true, true},
  {false, false, false, false},{true, true, true, false},{false, true, false, false},
  {true, false, false, true},{true, true, true, false}};
boolean[][] h = new boolean[][]{{false, false, false, false},{true, false, true, true},
  {false, true, false, false},{true, true, false, true},{false, true, false, false},
  {true, false, false, true},{false, false, false, false}};
boolean[][] i = new boolean[][]{{false, false, false, false},{true, true, false, true},
  {false, true, false, false},{true, false, false, true},{false, true, false, false},
  {true, false, true, true},{false, false, false, false}};
boolean[][] j = new boolean[][]{{true, false, true, false},{false, false, true, false},
  {true, false, false, false},{true, true, false, false},{false, false, false, false},
  {true, false, true, false},{true, true, false, false}};
boolean[][] k = new boolean[][]{{false, true, true, false},{true, true, false, false},
  {false, false, true, false},{true, false, false, false},{false, false, true, false},
  {true, true, false, false},{false, true, true, false}};
boolean[][] l = new boolean[][]{{false, false, false, false},{true, true, false, false},
  {false, false, true, false},{true, true, false, false},{false, true, false, false},
  {true, false, false, false},{true, true, true, false}};
boolean[][] m = new boolean[][]{{true, false, true, false},{true, true, true, true},
  {false, false, false, false},{true, true, true, true},{false, false, false, false},
  {true, false, false, true},{true, true, true, false }};
boolean[][] n = new boolean[][]{{true, false, false, false},{true, true, false, true},
  {false, false, false, false},{true, false, true, false},{false, false, true, false},
  {true, true, false, true},{false, true, false, false}};
boolean[][] o = new boolean[][]{{true, true, true, false},{true, false, false, true},
  {false, true, false, false},{false, true, true, false},{false, false, false, false},
  {true, false, false, true},{true, true, true, false}};
boolean[][] p = new boolean[][]{{true, true, true, false},{true, false, false, false},
  {false, true, true, false},{true, true, false, true},{false, false, true, false},
  {true, false, false, false},{false, false, false, false}};
boolean[][] q = new boolean[][]{{true, true, false, false},{false, false, true, false},
  {false, true, false, false},{true, false, false, false},{true, false, false, false},
  {false, false, true, true},{false, false, true, false}};
boolean[][] r = new boolean[][]{{true, true, true, false},{true, false, false, true},
  {false, false, true, false},{true, true, false, false},{false, false, false, false},
  {true, true, true, false},{false, false, false, false}};
boolean[][] s = new boolean[][]{{true, true, true, false},{true, false, false, false},
  {true, true, false, false},{false, false, false, false},{false, true, true, false},
  {false, false, false, true},{true, true, true, false}};
boolean[][] t = new boolean[][]{{true, true, true, false},{false, false, false, false},
  {false, false, false, false},{false, true, true, false},{false, false, false, false},
  {false, true, true, false},{false, false, false, false}};
boolean[][] u = new boolean[][]{{false, false, false, false},{true, true, true, true},
  {false, false, false, false},{true, true, true, true},{false, true, false, false},
  {true, false, false, true},{true, true, true, false}};
boolean[][] v = new boolean[][]{{false, false, false, false},{true, false, false, true},
  {true, false, true, false},{false, false, false, false},{false, false, false, false},
  {false, true, true, false},{false, true, false, false}};
boolean[][] w = new boolean[][]{{false, false, false, false},{true, false, false, true},
  {false, true, false, false},{false, true, true, true},{true, false, false, false},
  {false, false, false, false},{true, true, true, false}};
boolean[][] x = new boolean[][]{{false, false, false, false},{true, false, false, true},
  {true, false, true, false},{false, false, false, false},{true, false, true, false},
  {true, false, false, true},{false, false, false, false}};
boolean[][] y = new boolean[][]{{false, false, false, false},{true, false, false, true},
  {true, false, true, false},{false, true, false, false},{false, false, false, false},
  {false, false, true, false},{false, false, false, false}};
boolean[][] z = new boolean[][]{{true, false, false, false},{true, false, false, false},
  {true, true, false, false},{false, false, false, false},{false, true, true, false},
  {false, false, false, true},{false, false, true, false}};
boolean[][] space = new boolean[][]{{true, true, true, false},{true, false, false, true},
  {false, false, true, false},{true, true, true, true},{true, false, false, false},
  {true, false, false, true},{true, true, true, false}};
boolean[][] gap = new boolean[][]{{false, false, false, false},{false, false, false, false},
  {false, false, false, false},{false, false, false, false},{false, false, false, false},
  {false, false, false, false},{false, false, false, false}};
 
boolean[][] returnLetter(char cha){
  //if(cha == ' '){
  //  return gap;
  //}
  if(cha == 'a')
    return a;
  else if(cha == 'b')
    return b;
  else if(cha == 'c')
    return c;
  else if(cha == 'd')
    return d;
  else if(cha == 'e')
    return e;
  else if(cha == 'f')
    return f;
  else if(cha == 'g')
    return g;
  else if(cha == 'h')
    return h;  
  else if(cha == 'i')
    return i;  
  else if(cha == 'j')
    return j; 
  else if(cha == 'k')
    return k; 
  else if(cha == 'l')
    return l; 
  else if(cha == 'm')
    return m; 
  else if(cha == 'n')
    return n; 
  else if(cha == 'o')
    return o; 
  else if(cha == 'p')
    return p; 
  else if(cha == 'q')
    return q; 
  else if(cha == 'r')
    return r; 
  else if(cha == 's')
    return s; 
  else if(cha == 't')
    return t; 
  else if(cha == 'u')
    return u; 
  else if(cha == 'v')
    return v;
  else if(cha == 'w')
    return w;
  else if(cha == 'x')
    return x;
  else if(cha == 'y')
    return y;
  else if(cha == 'z')
    return z;
  else
    return space;
}
 
 
void setup(){
  size(700, 700);
  strokeWeight(3);
 
  bRecordingPDF = true;
 
}
 
void placePiece(int i, int j, boolean[][] letter){
  //if it's the first column we can't "check up"
  if(i == 0){ 
    //if it's the top left
    if( j == 0){
      pieces[i][j] = new MazePiece(letter, i + "" + j);
    }
    else{
      pieces[i][j] = new MazePiece(letter, i + "" + j);
      connectLeft(pieces[i][j - 1], pieces[i][j]);
    }
  }
  else{
    //if it's on the most left we can't "check left"
    if(j == 0){
      pieces[i][j] = new MazePiece(letter, i + "" + j);
      connectUp(pieces[i - 1][j], pieces[i][j]);
    }
    else{
      pieces[i][j] = new MazePiece(letter, i + "" + j);
      boolean upConnected = connectUp(pieces[i - 1][j], pieces[i][j]);
      if(!upConnected){
        connectLeft(pieces[i][j - 1], pieces[i][j]);
      }
      else{
        specialConnectLeft(pieces[i][j - 1], pieces[i][j]);
      }
    }
  }
}
 
//neighbor checking & id resetting
//the plan is to have everything that is connected share the same id
boolean canConnectLeft(MazePiece left, MazePiece right){
  return ((!left.edges[1][3] && !right.edges[1][0])
  || (!left.edges[3][3] && !right.edges[3][0])
  || (!left.edges[5][3] && !right.edges[5][0])); 
}
 
boolean canConnectUp(MazePiece up, MazePiece down){
  return ((!up.edges[6][0] && !down.edges[0][0])
  || (!up.edges[6][1] && !down.edges[0][1])
  || (!up.edges[6][2] && !down.edges[0][2])); 
}
 
boolean connectUp(MazePiece up, MazePiece down){
  if(canConnectUp(up, down)) 
  {
    up.downNeighbor = down;
    down.upNeighbor = up;
    println("up connected: " + up.id + " and " + down.id);
    down.id = up.id;
    return true;
  }
  return false;
}
 
//connects if possible
boolean connectLeft(MazePiece left, MazePiece right){
  if(canConnectLeft(left, right)) 
  {
    left.rightNeighbor = right;
    right.leftNeighbor = left;
    println("connected: " + left.id + " and " + right.id);
    right.id = left.id;
    return true;
  }
  return false;
}
 
boolean specialConnectLeft(MazePiece left, MazePiece right)
{
  if(canConnectLeft(left, right))
  {
    left.rightNeighbor = right;
    right.leftNeighbor = left;
    println("Recursive id setting starting");
    left.id = right.id;
    recursiveId(left);
    return true;
  }
  return false;
}
 
void recursiveId(MazePiece current){
  //base case - should halt if neighbors are all either null or already have the id
  if((current.leftNeighbor == null || current.leftNeighbor.id.equals(current.id))
  && (current.rightNeighbor == null || current.rightNeighbor.id.equals(current.id))
  && (current.upNeighbor == null || current.upNeighbor.id.equals(current.id))
  && (current.downNeighbor == null || current.downNeighbor.id.equals(current.id))){
    return;
  }
 
  if(current.leftNeighbor != null && !current.leftNeighbor.id.equals(current.id)){
    current.leftNeighbor.id = current.id;
    recursiveId(current.leftNeighbor);
  }
  if(current.rightNeighbor != null && !current.rightNeighbor.id.equals(current.id)){
    current.rightNeighbor.id = current.id;
    recursiveId(current.rightNeighbor);
  }
  if(current.upNeighbor != null && !current.upNeighbor.id.equals(current.id)){
    current.upNeighbor.id = current.id;
    recursiveId(current.upNeighbor);
  }
  if(current.downNeighbor != null && !current.downNeighbor.id.equals(current.id)){
    current.downNeighbor.id = current.id;
    recursiveId(current.downNeighbor);
  }
}
 
void carveEdges(){
  ArrayList<int[][]> edges = new ArrayList<int[][]>();
  for(int i = 0; i < strlen / 2  ; i++){
 
    int[][] ele = generateEdge();
    //loop through arraylist to check for duplicates
    //the big O on this program must be awful r i p
    while(listContains(edges, ele)){
      ele = generateEdge();
    }
    edges.add(ele);
  }
 
  for(int i = 0; i < edges.size(); i++){
    int[] fromIndex = edges.get(i)[0];
    int[] toIndex = edges.get(i)[1];
    MazePiece f = pieces[fromIndex[0]][fromIndex[1]];
    MazePiece t = pieces[toIndex[0]][toIndex[1]];
    println(fromIndex[0] +" " +fromIndex[1] + " to  " + toIndex[0] +" " + toIndex[1]);
 
    if(!(f.id.equals(t.id))){
 
      t.id = f.id;
      recursiveId(t);
      connect(fromIndex, toIndex);
    }
  }
}
 
void connect(int[] from, int[] to){
  //if they're at different heights, it's an up down kinda situation
  MazePiece f = pieces[from[0]][from[1]];
  MazePiece t = pieces[to[0]][to[1]];
 
  if(from[0] < to[0]){ t.edges[0][2] = false; f.edges[6][2] = false; f.downNeighbor = t; t.upNeighbor = f; } else if(from[0] > to[0]){
    t.edges[6][2] = false;
    f.edges[0][2] = false;
    f.upNeighbor = t;
    t.downNeighbor = f;
  }//if they're at different columns, it's a left right kind of situation
  else if(from[1] < to[1]){ t.edges[3][0] = false; f.edges[3][3] = false; f.rightNeighbor = t; t.leftNeighbor = f; } else if(from[1] > to[1]){
    t.edges[3][3] = false;
    f.edges[3][0] = false;
    f.leftNeighbor = t;
    t.rightNeighbor = f;
  }
  else{
    println("oh no! something is very very wrong");
  }
}
 
int[][] generateEdge(){
 
    //like this is unneccesarily complicated but it's late and I'm not thinking straight
    int[] from = new int[]{(int)random(hei), (int)random(wid)};
    while(pieces[from[0]][from[1]] == null){
      from = new int[]{(int)random(hei), (int)random(wid)};
    }
 
    int updown = random(-0.999999, 1) < 0 ? -1 : 1;
    int leftright = random(-0.999999, 1) < 0 ? -1 : 1;
    if(from[0] == 0) updown = 1;
    if(from[0] == hei - 1) updown = -1;
    if(from[1] == 0) leftright = 1;
    if(from[1] == wid - 1) leftright = -1;
 
    //choose an orientation for the edge
    boolean horzEdge = random(-0.9999, 1) < 0 ? true : false;
    if(horzEdge){
      updown = 0;
    }
    else{
      leftright = 0;
    }
 
    int[] to = new int[]{from[0] + updown, from[1] + leftright};
    while(pieces[to[0]][to[1]] == null){
      updown = random(-0.999999, 1) < 0 ? -1 : 1;
      leftright = random(-0.999999, 1) < 0 ? -1 : 1;
      if(from[0] == 0) updown = 1;
      if(from[0] == hei - 1) updown = -1;
      if(from[1] == 0) leftright = 1;
      if(from[1] == wid - 1) leftright = -1;
 
      //choose an orientation for the edge
      horzEdge = random(-0.9999, 1) < 0 ? true : false;
      if(horzEdge){
        updown = 0;
      }
      else{
        leftright = 0;
      }
      to = new int[]{from[0] + updown, from[1] + leftright};
    }
 
    return new int[][]{from, to};
}
 
boolean listContains(ArrayList<int[][]> arr, int[][] ele){
  for(int i = 0; i < arr.size(); i ++){
    if(arr.get(i)[0][0] == ele[0][0] && arr.get(i)[0][1] == ele[0][1]
    && arr.get(i)[1][0] == ele[1][0] && arr.get(i)[1][1] == ele[1][1]){
      return true;
    }
  }
  return false;
}
 
 
//--------------drawing the final result----------------------------------
void drawMaze(){
  for(int i = 0; i < hei; i ++){
    for(int j = 0; j < wid; j++){
        if(pieces[i][j] == null) break;
        println(i * hei + j, strlen);
        if(i * wid + j == strlen - 1){
          pieces[i][j].edges[5][3] = false;
        }
        drawPiece(i, j);
        //text(pieces[i][j].id, j * (pieceWidth-1) * step, i * (pieceWidth - 1) * step + 25);
    }
  }
}
 
void drawPiece(int i, int j){
      for(int k = 0; k < pieceHeight; k ++){
        for(int l = 0; l < pieceWidth; l++){
      if(k % 2 == 0 && pieces[i][j].edges[k][l]){
        drawHorzLine(i, j, k, l);
      }
      else if(pieces[i][j].edges[k][l]){
        drawVertLine(i, j, k, l);
      }
    }
  }
}
 
void drawHorzLine(int i, int j, int k, int l){
  line(j * (pieceWidth-1) * step +  step * l, step * (k/2) + (i * (pieceWidth -1) * step),  
    j * (pieceWidth - 1) * step + step * l + step, step * (k/2) + (i * (pieceWidth - 1) * step));
}
 
void drawVertLine(int i, int j, int k, int l){
   line(j * (pieceWidth -1)* step +  step * l, step * (k/2) + (i * (pieceWidth - 1) * step), 
   j * (pieceWidth-1) * step + step * l, step * (k/2) + step + (i * (pieceWidth - 1) * step));
}
 
void draw() {
  if (bRecordingPDF) {
    background(255); // this should come BEFORE beginRecord()
    beginRecord(PDF, "myName_" + pdfOutputCount + ".pdf");
 
    //--------------------------
 translate(50, 50);
  String str = "this chicken is so undercooked a skilled vet could still save him-gordon";
  strlen = str.length();
  hei = strlen / wid + 1;
  int leftover = strlen % wid;
 
  char[] message = str.toCharArray();
 
  pieces = new MazePiece[hei][wid];
 
  for(int i = 0; i < hei; i++){
    for(int j = 0; j < wid; j++){ if(i * wid + j >= message.length){
        pieces[i][j] = null;
      }
      else 
      { placePiece(i, j, returnLetter(message[i * wid + j])); }
    }
  }
 
  carveEdges();
 
  drawMaze();
  //stroke(255, 0, 0);
 
  if(leftover == 0){
    line(0, 0, (pieceWidth -1) * step * wid, 0);
    line(0, (step * (pieceWidth - 1))/3, 0, (pieceWidth -1) * step * (hei - 1));
    line(0, (pieceWidth -1) * step * (hei - 1), (pieceWidth - 1) * step * wid, (pieceWidth -1) * step * (hei - 1));
    line((pieceWidth - 1) * step * wid, 0, (pieceWidth - 1) * step * wid, (pieceWidth -1) * step * (hei - 1) - (step * (pieceWidth - 1))/3);
  }
  else{
    line(0, 0, (pieceWidth -1) * step * wid, 0);
    line(0, (step * (pieceWidth - 1))/3, 0, (pieceWidth -1) * step * hei);
    line(0, (pieceWidth -1) * step * hei, (pieceWidth - 1) * step * leftover, (pieceWidth -1) * step * hei);
    line((pieceWidth - 1) * step * leftover, (pieceWidth -1) * step * (hei - 1), (pieceWidth - 1) * step * leftover, (pieceWidth -1) * step * hei - (pieceWidth - 1)* step /3);
    line((pieceWidth - 1) * step * leftover, (pieceWidth -1) * step * (hei - 1), (pieceWidth - 1) * step * wid, (pieceWidth -1) * step * (hei-1));
    line((pieceWidth - 1) * step * wid, 0, (pieceWidth - 1) * step * wid, (pieceWidth -1) * step * (hei - 1));
  }
 
    //--------------------------
 
    endRecord();
    bRecordingPDF = false;
    pdfOutputCount++;
  }
}