My Final Project was a maze generator that you can solve!
How to Play: Wait for the maze generator to run in the background, once the loading screen is gone you are ready to play. Use your arrow keys to navigate the green dot through the maze to the red dot.
//Erin Fuller
//Section A
//efuller@andrew.cmu.edu
//Final Project
//This Maze Generator + Game uses first depth search and recursive backtracking
//to generate the maze.
//https://en.wikipedia.org/wiki/Maze_generation_algorithm#Depth-first_search
//https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
var cols, rows; // dividing canvas into grid array
var w = 30; // cell width
var grid = [];
var sX = 0; // solver x position
var sY = 0; // solver y position
var distance = 0, longdist = 0;
var longest;
var current;
var mazeGen = false; //maze generator not finished
var stack = []; // stack of current cells maze is genrating
// wall array directions
var up = 0;
var right = 1;
var down = 2;
var left = 3;
function setup() {
createCanvas(480, 480);
cols = width / w;
rows = height / w;
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
var cell = new Cell(j, i); //creates cell object
grid.push(cell); //pushes object into grid array
}
}
current = grid[0]; //starts grid at top left corner
current.visited = true;
}
function draw() {
mazeDraw();
if (mazeGen) {
solve(); //when maze is finsihed generating it calls a solve function
} else {
preText(); //shows instruction while maze is generating
}
}
function mazeDraw() {
background(165, 202, 218); //LIGHT BLUE
for (var i = 0; i < grid.length; i++) {
grid[i].show();
}
// Step 1: randomly choose of the unvisited neighbors
var next = current.checkNeighbors();
if (next) {
next.visited = true;
// Step 2: push the current cell to the stack
stack.push(current);
// Step 3: Remove wall between current cell and chosen cell
removeWalls(current, next);
// Step 4: Make chosen cell the current cell and mark as visited
current = next;
distance++;
} else {
if (stack.length > 0) {
current = stack.pop();
} else {
current = grid[0];
mazeGen = true;
distance;
}
}
if (longdist < distance) {
longdist = distance;
longest = current;
}
}
function index(i, j) {
if (i < 0 || j < 0 || i > cols - 1 || j > rows - 1) {
return -1; //return negative/invalid index
} else {
return i + j * cols;
}
}
function Cell(i, j) {
this.i = i;
this.j = j;
this.walls = [true, true, true, true]; // N, E, S, W
this.visited = false;
this.checkNeighbors = function() {
var neighbors = [];
var n = grid[index(i, j - 1)]; // top line (N)
var e = grid[index(i + 1, j)]; //right line (E)
var s = grid[index(i, j + 1)]; //bottom line (S)
var w = grid[index(i - 1, j)]; //left line (W)
//Avoided the "conditional AND operator" because WordPress converts it to a "logical ND" operator
if (n) {
if (!n.visited) {
neighbors.push(n);
}
}
if (e) {
if (!e.visited) {
neighbors.push(e);
}
}
if (s) {
if (!s.visited) {
neighbors.push(s);
}
}
if (w) {
if (!w.visited) {
neighbors.push(w);
}
}
if (neighbors.length > 0) {
var r = floor(random(0, neighbors.length));
return neighbors[r];
} else {
return undefined;
}
}
this.show = function() {
var x = this.i * w;
var y = this.j * w;
stroke(0);
strokeWeight(1);
if (this.walls[up]) {
line(x, y, x + w, y); // top line (N)
}
if (this.walls[right]) {
line(x + w, y, x + w, y + w); //right line (E)
}
if (this.walls[down]) {
line(x + w, y + w, x, y + w); //bottom line (S)
}
if (this.walls[left]) {
line(x, y, x, y + w); //left line (W)
}
}
}
function removeWalls(a, b) {
var x = a.i - b.i; //x = diffrence between current cell and its neighbor
if (x === 1) {
a.walls[left] = false; //remove left/west wall on current
b.walls[right] = false; //removes right/east wall on neighbor
} else if (x === -1) {
a.walls[right] = false; //remove right/east wall on current
b.walls[left] = false; //removes left/west wall on neighbor
}
var y = a.j - b.j; //x = diffrence between current cell and its neighbor
if (y === 1) {
a.walls[up] = false; //remove up/north wall on current
b.walls[down] = false; //removes down/south wall on neighbor
} else if (y === -1) {
a.walls[down] = false; //remove down/south wall on current
b.walls[up] = false; //removes up/north wall on neighbor
}
}
function keyPressed() {
var solver = grid[index(sX, sY)];
//Avoided the "conditional AND operator" because WordPress converts it to a "logical ND" operator
if (keyCode === LEFT_ARROW) {
if (!solver.walls[left]) {
sX --;
}
}
if (keyCode === RIGHT_ARROW) {
if (!solver.walls[right]) {
sX ++;
}
}
if (keyCode === UP_ARROW) {
if (!solver.walls[up]) {
sY --;
}
}
if (keyCode === DOWN_ARROW) {
if (!solver.walls[down]) {
sY ++;
}
}
}
function solve() {
var a = 0.5 * w;
noStroke();
fill(214, 247, 52); //green "solver" dot
var gX = a + (w * sX); //controlled by arrow keys
var gY = a + (w * sY);
ellipse(gX, gY, a, a);
fill(250, 29, 59); //red "end" dot
var rX = a + (w * longest.i);
var rY = a + (w * longest.j);
ellipse(rX, rY, a, a);
if (dist(gX, gY, rX, rY) < a) { //if green dot reaches red dot
winner();
}
}
function preText() {
fill(165, 202, 218); //blue background
rect(0, 0, width, height);
textAlign(CENTER);
noStroke();
fill(255);
textSize(75);
text('Get Ready.', width / 2, height / 2);
textSize(20);
text('The maze is loading. Use your arrow keys to navigate the green dot to the red dot.',
width / 2 - 150, height / 2 + 45, 300, 200);
}
function winner() {
fill(165, 202, 218); //blue background
rect(0, 0, width, height);
textAlign(CENTER);
noStroke();
fill(255);
textSize(87);
text('YOU WON!', width / 2, height / 2);
textSize(25);
text('Refresh to play again.', width / 2, height / 2 + 45);
}
This was a big task for me. The maze was generated using Recursive Backtracking, a form of First Depth Search (more info can be found on this Wikipedia page). In short, the canvas is gridded into cells and the generator randomly chooses a neighbor cell to visit, removing the wall between it, until all the cells have been visited and a maze is created. A lot of this was new concepts for me, so big thanks to Professor Dannenberg for pointing me in the right direction and Dan Shiffman‘s Coding Train for having some material on this.
The end goal is generated to be the furthest point along the maze from the origin. The start/player begins in the top left corner and moves along the maze to the red dot. Getting the solver dot to stay inside the lines was probably the biggest challenge.
Some Errors: The red dot that signifies the target is supposed to be the furthest distance in the maze from the start. Sometimes it works, but sometimes the target is very close to the origin. I think this is because it may classify the last unvisited cell as the furthest even though the may be very close.
]]>The first project, Orbicular Geode Puzzle, I found was made by Nervous System. It’s a puzzle that is computer generated to represent a slice of an algorithmic geode. Each puzzle is unique, emerging from a computer simulation that creates natural variations in the shape, pieces, and image. The way the puzzle is cut, a dense, maze-like pattern with extreme intertwining and high piece count, makes it an extremely hard puzzle to solve.
It is similar to my final project because I’m making a generated maze. Though I probably won’t be able to make something as complex as this, it is a nice project to see how beautiful a generated puzzle can be.
My second project, while not a highly technical project, explores mazes even more. Robert Morris, American sculptor, conceptual artist, and writer, is regarded as one of the most prominent theorists of Minimalism. The Philadelphia Labyrinth was a site-specific art installation. While this is still a contemporary project, I think it’s interesting how much labyrinths, mazes, and puzzles have been a part of the human consciousness since ancient times.
]]>
As a kid I always loved maze puzzles; there was even a point in early middle school where I would spend hours not paying attention to school and create my own mazes on graph paper and come back a week or two later to try to solve them. Because I was creating them I had some underlying logic that I was unconsciously implanting, so I solved them pretty quick.
For my final project, I want to create a program that randomly generates a maze that users can solve. I think this would be done with turtle graphics or some sort of recursion algorithm. If time permits, I would like to add animation to the users’ progress in completing the maze and possibly sound.
The piece “Order from Chaos” is a beautiful composition of computed visual and audio work. The track was produced by London based electronica and techno producer Max Cooper. The track was originally inspired by a moment where Cooper was captivated by the sound of hard rain hitting a roof window at his apartment. He recorded the sound with binaural mics, microphones that capture audio the same way your real ears hear sounds, and used the initial recording as a seed for the rest of the track. The pattern from the raindrops based on their closest structure, that creates an emergent rhythm, an initially detailed and chaotic form which slowly develops into something with a recognizable structure.
While the prompt is focused on computer music, it would not be fair not to note the mesmerizing visual animations done by French-based Houdini Fx artist, motion graphic artist and Director, Maxime Causeret.
//Erin Fuller
//SectionA
//efuller@andrew.cmu.edu
//Project 11
var t = [0, 1, 2]; //start w/ 3 turtles
var targetX = 0;
function setup() {
createCanvas(480, 480);
background(0);
for (var i = 0; i < t.length; i++) { //initialize turtles
t[i] = makeTurtle(0, height / 2 + random(-100, 100));
t[i].setColor(color(random(100), random(255), random(255), 50));
}
frameRate(40); //slow down turtle!!!
}
function draw() {
targetX += 1; //updates target with each frame
var targetY = (width / 2) - 90 * sin(radians(targetX)); //sine wave
noStroke(); //makes target curve "invisible"
point(targetX, targetY);
for (var j = 0; j < t.length; j++) {
t[j].penDown();
t[j].setWeight(4);
t[j].forward(1.5);
t[j].turnToward(targetX, targetY, 2); //orient towards sine wave
t[j].left(random(-5, 5)); //adds noise
if (targetX === width) { //restarts when reachinf edge of screen
targetX = 0; //restart target
for (var k = 0; k < t.length; k++) {
t[k].penUp(); //stops turtles moves them back
t[k].goto(0, height / 2 + random(-100, 100));
t[k].penDown(); //restart turtle
}
}
}
}
function mousePressed() {
var newTurtle = makeTurtle(mouseX,mouseY); //making new turtle at mouse press
newTurtle.setColor(color(random(100), random(255), random(255), 50));
t.push(newTurtle); //push turtle onto array
}
//------------------------------------------------------------------------------------
// makeTurtle(x, y) -- make a turtle at x, y, facing right, pen down
// left(d) -- turn left by d degrees
// right(d) -- turn right by d degrees
// forward(p) -- move forward by p pixels
// back(p) -- move back by p pixels
// penDown() -- pen down
// penUp() -- pen up
// goto(x, y) -- go straight to this location
// setColor(color) -- set the drawing color
// setWeight(w) -- set line width to w
// face(d) -- turn to this absolute direction in degrees
// angleTo(x, y) -- what is the angle from my heading to location x, y?
// turnToward(x, y, d) -- turn by d degrees toward location x, y
// distanceTo(x, y) -- how far is it to location x, y?
function turtleLeft(d) {
this.angle -= d;
}
function turtleRight(d) {
this.angle += d;
}
function turtleForward(p) {
var rad = radians(this.angle);
var newx = this.x + cos(rad) * p;
var newy = this.y + sin(rad) * p;
this.goto(newx, newy);
}
function turtleBack(p) {
this.forward(-p);
}
function turtlePenDown() {
this.penIsDown = true;
}
function turtlePenUp() {
this.penIsDown = false;
}
function turtleGoTo(x, y) {
if (this.penIsDown) {
stroke(this.color);
strokeWeight(this.weight);
line(this.x, this.y, x, y);
}
this.x = x;
this.y = y;
}
function turtleDistTo(x, y) {
return sqrt(sq(this.x - x) + sq(this.y - y));
}
function turtleAngleTo(x, y) {
var absAngle = degrees(atan2(y - this.y, x - this.x));
var angle = ((absAngle - this.angle) + 360) % 360.0;
return angle;
}
function turtleTurnToward(x, y, d) {
var angle = this.angleTo(x, y);
if (angle < 180) {
this.angle += d;
} else {
this.angle -= d;
}
}
function turtleSetColor(c) {
this.color = c;
}
function turtleSetWeight(w) {
this.weight = w;
}
function turtleFace(angle) {
this.angle = angle;
}
function makeTurtle(tx, ty) {
var turtle = {x: tx, y: ty,
angle: 0.0,
penIsDown: true,
color: color(128),
weight: 1,
left: turtleLeft, right: turtleRight,
forward: turtleForward, back: turtleBack,
penDown: turtlePenDown, penUp: turtlePenUp,
goto: turtleGoTo, angleto: turtleAngleTo,
turnToward: turtleTurnToward,
distanceTo: turtleDistTo, angleTo: turtleAngleTo,
setColor: turtleSetColor, setWeight: turtleSetWeight,
face: turtleFace};
return turtle;
}
My composition is three turtles chasing a sine wave, but the turtles can never catch it fully because of added noise that makes them wander off track. If you click your mouse, you add a new turtle!
//Erin Fuller
//SectionA
//efuller@andrew.cmu.edu
//Project 10
var terrainSpeed1 = 0.00009; //background mountain speed
var terrainDetail1 = 0.006; //background mountain detail
var terrainSpeed2 = 0.00025; //foreground mountain speed
var terrainDetail2 = 0.005; //foreground mountain detail
var terrainSpeed3 = 0.00025; //foreground plane speed
var terrainDetail3 = 0.0015; //foreground plane detail
var img;
var gFrameCount = 0;
function preload() {
img = loadImage("https://i.imgur.com/mO1FDPJ.png"); //preload image
}
function setup() {
createCanvas(480, 480);
frameRate(10);
}
function draw() {
//sky
colorMode(HSB);
var s = 55; // saturation 55
var b = 100; //brightness 100
gFrameCount = gFrameCount + 1;
if (gFrameCount > 600) {
gFrameCount = 0;
} //add frame count, restart after 600
if (gFrameCount > 300) {
var s = map(gFrameCount, 300, 600, 85, 55);
var b = map(gFrameCount, 300, 600, 25, 100);
var m = map(gFrameCount, 300, 600, 25, 65);
} else {
var s = map(gFrameCount, 0, 300, 55, 85);
var b = map(gFrameCount, 0, 300, 100, 25);
var m = map(gFrameCount, 0, 300, 65, 25);
} //changes sky background + mountains to night
background(214, s, b);
//sun & moon
var centerx = width / 2;
var centery = 170;
var radius = 120;
var d = 45 + random(-2, 2); //makes sun and moon more fun
var rAngle = map(frameCount, 0, 600, 0, 360) + 45; //
noStroke();
line(centerx, centery, centerx + x, centery - y); //rotating line w circles at end
var sX = cos(radians(rAngle)) * radius;
var sY = sin(radians(rAngle)) * radius;
ellipseMode(CENTER);
fill('yellow');
ellipse(centerx + sX, centery - sY, d, d); //sun
var mX = cos(radians(-rAngle)) * radius; //neagtive to go in opposite of sun
var mY = sin(radians(-rAngle)) * radius;
fill('white');
ellipse(centerx + mX, centery - mY, d, d); //moon
//background-mountain
noStroke();
fill(32, 60, m - 5);
beginShape();
vertex(0, height);
for (var x = 0; x < width; x++) {
var t = (x * terrainDetail1) + (millis() * terrainSpeed1);
var y = map(noise(t), 0, 1, height / 12, height / 3);
vertex(x, y);
}
vertex(width, height);
endShape();
//foreground-mountain
fill(32, 45, m);
beginShape();
vertex(0, height);
for (var z = 0; z < width; z++) {
var f = (z * terrainDetail2) + (millis() * terrainSpeed2);
var y = map(noise(f), 0, 1, height / 12, height / 2);
vertex(z, y);
}
vertex(width, height);
endShape();
//foreground-plane
fill(32, 30, m + 20);
beginShape();
vertex(0, height);
for (var z = 0; z < width; z++) {
var p = (z * terrainDetail3) + (millis() * terrainSpeed3);
var y = map(noise(p), 0, 1, height / 2, height * .75);
vertex(z, y);
}
vertex(width, height);
endShape();
//foreground-train
fill(10);
beginShape(); // clockwise
vertex(0, 480);
vertex(0, 0);
vertex(480, 0);
vertex(480, 480);
var a = 20;
var b = 40;
var c = 60
beginContour(); // counter clockwise
vertex(b, a); //left-up
bezierVertex(a, a, a, a, a, b);
vertex(a, height - b - c); //left-down
bezierVertex(a, height - a - c, a, height - a - c, b, height - a - c);
vertex(width - b, height - a - c); //right-down
bezierVertex(width - a, height - a - c, width - a, height - a - c, width - a, height - b - c);
vertex(width - a, b); //right up
bezierVertex(width - a, a, width - a, a, width - b, a);
endContour();
endShape(CLOSE);
rect(0, 115, width, 5);
rect(0, 210, width, 5);
rect(0, 305, width, 5);
noFill();
colorMode(RGB, 225);
stroke(139, 186, 213, 130);
strokeWeight(10);
line(60, 100, 120, 40); //"window glares"
line(62, 73, 93, 42);
line(95, 95, 130, 59);
line(426, 322, 387, 361); //"window glares"
line(443, 328, 390, 381);
line(451, 344, 412, 382);
//foreground cowboy
image(img, -90, 140, img.width * .25, img.height * .25);
}
I generated a landscape that both travels through space and time. The cowboy is watching the desert pass by from the train, as well as the day change to night and back.
]]>Emily Gobeille is an artist and renowned designer who specializes in merging technology and design to create rich and immersive design experiences. She is a Partner and Creative Director of Design I/O, and due to her unbound energy and affinity for telling stories, she tends to create of high-end but playful interactive installations for children. With an emphasis on meaningful interaction and systems built to support open play and discovery, her work creates a sense of wonder and delight.
The Carnival Interactive Aquarium, commissioned by Arnold R&D, was installed in storefront windows in 6 cities across the US. Gobeille used computer vision to cause the seascape to react to the motion of a user – seaweed will sway and fish will scatter – who can dial in with any mobile device and create a fish using their voice. Users play a game with the installation in real time through voice and phone keypad. I think this installation, though it was created as an advertisement, is really neat in how the public can interact with it.
Video of Installation, 2009
//Erin Fuller
//SectionA
//efuller@andrew.cmu.edu
//Project 09
var img;
function preload() {
img = loadImage("https://i.imgur.com/4QKnX6El.jpg"); //imgur image link
}
function setup() {
createCanvas(480, 480);
noStroke();
background(0);
img.loadPixels();
}
function draw() {
var imgW = img.width;
var imgH = img.height;
for (var i = 0; i < imgW; i++) { //searches x pixels
for (var j = 0; j < imgH; j++){ //searches y pixels
var c = img.get(i, j); //gets colors for all pixels
var val = brightness(c); //gets brightness from colors
var s = map(val, 0, 255, 0, 20); //maps brightness to circle size
var pix = img.get(i, j); // gets color at each pixel
fill(pix); //colors pixels
if (i%12 === 0 & j%12 === 0) {
ellipse(i, j, s, s); //draws ellipse at every 12th pixel
}
}
}
}
I used a picture of my friend Mae (original photo shown below) as a base for my computational portrait. To create the resulting computational image, I pointillized the photo by drawing the circles every 12 pixels (in both x and y-direction). Each circle uses the color of the pixel at the center of each ellipse, but the size is determined by the brightness of the pixel. Therefore, as you can see she is wearing a black shirt, so those circles are much smaller than her face.
With a better photo, a more frontal headshot photo, I think the generated image may have a more successful result.
]]>I found Jenni Lee’s Looking Outward for week seven, which was focused on computational information visualization. She chose to exam the project titled, “The Creatures of Prometheus – Generative Visualisation of Beethoven’s Ballet with Houdini” by Simon Russell. The project visualizes how Bethoven’s 1801 Ballet is conducted in a symphony orchestra.
“The Creatures of Prometheus – Generative Visualisation of Beethoven’s Ballet with Houdini” by Simon Russell, (2017)
I think this piece is not only beautiful in terms of execution, but because of how well it communicates the information, it also could be used as an educational piece. I remember in elementary school going on a field trip every year to the Naples Philharmonic and that was pretty much my only exposure to chamber music, albeit still a lot of exposure. For those who do not have the opportunity to have that experience, this visualization is a fantastic way, in a much more modern approach than traditional music education, to show how orchestras work and are put together.
]]>
Original Eyeo 2014 Video
The pair Mimi Son and Elliot Woods have a studio called Kimchi and Chips. Son is a Seoul based artist specializing in storytelling through interaction design. She studied Interaction Design at Copenhagen Institute of Interaction Design and finished her Master of Art in Interaction Design at the Landsdown Centre of Middlesex University in London. Woods is a digital media artist from the UK. He was educated in physics and is using his background to produce sense-able phenomena from abstract domains.
They focus mostly on exploring the differences between material and immaterial. Using light as an immaterial to create material, many of their works are results of projection mapping techniques.
LINE SEGMENTS SPACE, 2013
My favorite piece is called “LINE SEGMENTS SPACE”. The gallery space is filled with a three-dimensional web of string. With careful projection mapping, the strings are lit up to create dynamic imagery. Through the use of light as immaterial, a complex form emerges.
]]>