Week 8 Reading Response Dachi

The stories of Margaret Hamilton’s work on the Apollo software and Don Norman’s article on the role of aesthetics in design provide valuable insights into the complex relationship between form, function, and user experience.
Hamilton’s experiences showcase the critical importance of secure programming by anticipating edge cases. “Hamilton wanted to add error-checking code to the Apollo system that would prevent this from messing up the systems. But that seemed excessive to her higher-ups.” This example highlights the need for designers to think through all the ways a product might be misused, even if it means going out of their way for additional work. This is actually a core part of every group work we do. Since one student might specialize in something, it’s their responsibility to convey complexities to a manager or the leader. This of course assumes that leader is capable of listening and trusting their group members’ expertise. Unfortunately, most companies nowadays work around maximizing profit and such thoughts are the last things that come to their mind.
The pushback Hamilton faced raises questions about the biases we bring to the design process. The belief that “astronauts would not make any mistakes” is plain wrong. Norman’s article suggests that aesthetic preferences can vary significantly across cultures and individuals – the same as “Three Teapots”. This variability means that as designers, we must constantly challenge our own assumptions and try to broaden our perspective. We should allow for all possibilities to happen and design a product for the end user (who might happen to be an astronaut) that works without major flaws. Norman’s article also suggests that that an attractive design can enhance usability, but not replace it entirely. For example, our current smartphone UIs are much more attractively designed than a few years ago. Even so, this raises the complexity of usability. I have personally downloaded older more simplified launchers for my grandmother, simply because it is so much easier to use.
All in all, combining usability with aesthetics while having the end user in mind is clearly the way to go. Hamilton’s approach to software engineering shows that innovative work pays off, as the result was truly out of this world.

Lord of The Maze – Midterm – Dachi Tarughishvili

Sketch (Fullscreen) https://editor.p5js.org/dt2307/full/vrBxuAsfN

(I have embedded here just in case but please open it in separate tab for everything to properly work (most importantly audio))

Idea: Lord of the Rings inspired game, where a player takes initiative to escape the maze, avoid the orcs and flying monster, find the ring and reach the mount of doom

Project summary: This game takes inspiration from the epic fantasy novel The Lord of the Rings, which my previous project Eye of Sauron was also based on. In this maze-style game, the main character Frodo must navigate through a maze and reach Mount Doom, the volcanic location in Mordor where the powerful ring was forged and the only place it can be destroyed. Roaming orcs patrol the maze pathways, which Frodo must avoid. Coming into direct contact with an orc reduces Frodo’s health. If Frodo loses all three health points, the game is over. If Frodo successfully reaches Mount Doom in time, the player wins the game and an image is displayed. The goal is to guide Frodo through the maze while evading orcs in order to make it to Mount Doom and destroy the ring. However, there is a catch, if you reach mount of doom without obtaining the ring, flying monster will start chasing you and at that point you should get the ring as soon ass possible to evade it. Once you capture the ring, Sauron animation will be displayed (based on my previous project with the perlin noise after Coding Train intro). After that you can see game become more gloomy as colors start to change, background included. Fortunately, due to magical powers of the ring you are granted an invisibility buff which lasts for certain amount of time. The visual cue is there for player by reducing Frodo’s transparency as well as audio cue which gets more frequent with more pulses indicating when you are gonna run out. Finally, you are able to reach mount of doom and destroy the ring if you get through the remaining orcs!

Inspiration: this game is inspired by lord of the rings movies (books):  The Fellowship of the Ring (2001), The Two Towers (2002), and The Return of the King (2003). I want to recreate an experience where player gets to have their own journey, traversing long distance, making strategic choices, avoiding the danger and reaching destination similar to what happens in the movies.

Visuals: the maze itself is black on green canvas. Characters have their own images (orc, frodo, mount of doom etc.). They are in pixel art style to give players a nostalgic feeling which also makes whole game work on this platform much smoother. The main menu screen as well as instructions and game won game over screen are AI generated, but text on top is using custom font.

Process and Challenges: I made sure to utilize an object-oriented approach. There were several development hurdles. Firstly, after designing the maze layout and slowly incorporating it into code to test functionality, I struggled greatly with collision detection (characters could access the maze improperly from certain sides) which took substantial time to correct. Additionally, programming the repetitive orc movements to patrol the maze appropriately relied heavily on trial-and-error to determine optimal pathways. (And lots of Googling!). Last few days, I also added sounds which were not too difficult but took some time to pick right soundtracks and make it working. Volume slider was a bit tricky as I head to read documentation online because I did not like the way its default behavior worked. I also added countdown which lets player see their current time as well as total time they took to beat the challenge. Additionally, I fixed issue with ring, and volume slider being displayed over game over screen and such. I added even more soundtracks, for getting the ring and spawning the ring. Moreover, I implemented features such as flying monster which spawns and moves towards frodo if he goes to mount of doom without picking up the ring. Upon picking up the ring, I added a feature based on my last project where eye of sauron animation gets displayed (which was done using perlin noise). This comes with change in background as well as another feature – Invisibility. In simple terms, frodo becomes more transparent visually, a sound for invisibility starts playing and in specific timeframe he is immune to enemies. I added another orc near ring to make getting it more challenging. Last but not least, ring gets spawned only if Frodo reaches certain area in the map, to ensure that player can’t just camp at base and wait for ring to spawn if there was a timer instead, making game much simpler.

Here are some development progress pictures (I have not included every one of them) :

Code:

I have separate JS classes for different functions of the game.

Lets go over most of them (briefly, more details are in code comments):

Drawing UI Class: takes care of top bar with health, volume and timer texts.

function drawUI() {
  // Draw Health text
  fill(255);
  textSize(14);
  noStroke();
  text("Lives: " + playerHealth, 55, 11);

  // Draw Volume text
  fill(255);
  textSize(14);
  noStroke();
  text("Volume:", 150, 11);
  // Make sure volume slider is visible
  volumeSlider.style('display', 'block');

  // Draw Timer
  fill(255);
  textSize(14);
  text("Time: " + playTime.toFixed(1) + "s", width - 60, 11);

  // Set volume based on slider value
  initialVolume = volumeSlider.value();
  backgroundMusic.setVolume(initialVolume);
}

Orc class: takes care of spawning as well as moving orcs (also makes sure they don’t go in the maze)

class Orc {
  constructor(pointA, pointB, spawn) {
    this.pointA = pointA; //start
    this.pointB = pointB; //end
    this.size = 20;
    this.speed = 1.2;

    //initial spawn
    this.x = spawn.x;
    this.y = spawn.y;

    // target
    this.currentTarget = this.pointA;
  }

  display() {
    image(orcImg, this.x, this.y, this.size, this.size);
  }

  move() {
    let dx = this.currentTarget.x - this.x;
    let dy = this.currentTarget.y - this.y;
    let length = sqrt(dx * dx + dy * dy); //direction vector

    if (length > 0) {
      dx /= length; //normalize vector for consistent speed
      dy /= length;
      
      //calculate new position
      let newPosX = this.x + dx * this.speed;
      let newPosY = this.y + dy * this.speed;

      if ( //if new position is in bound and does not collide with walls
        newPosX > 0 &&
        newPosX < width - this.size &&
        newPosY > 0 &&
        newPosY < height - this.size &&
        maze[getRow(newPosY)][getCol(newPosX)] !== '#'
      ) {
        this.x = newPosX;
        this.y = newPosY;

        // check if orc reached target
        if (dist(this.x, this.y, this.currentTarget.x, this.currentTarget.y) < this.speed) {
          // switch points
          this.currentTarget = this.currentTarget === this.pointA ? this.pointB : this.pointA;
        }
      }
    }
  }
}

function generateLevel() {
  orcs = [];
  orcs.push(new Orc({ x: 28, y: 350 }, { x: 28, y: 180 }, { x: 28, y: 180 }));
  orcs.push(new Orc({ x: 605, y: 100 }, { x: 605, y: 400 }, { x: 605, y: 180 }));
  orcs.push(new Orc({ x: 452, y: 420 }, { x: 452, y: 250 }, { x: 452, y: 250 }));
  orcs.push(new Orc({ x: 260, y: 605 }, { x: 455, y: 605 }, { x: 455, y: 605 }));
  orcs.push(new Orc({ x: 300, y: 100 }, { x: 200, y: 100 }, { x: 200, y: 100 }));
  // orcs and their pathways
}

Player class: initializes player, as well as deals with maze collision and invisibility buff.

class Player {
  constructor() {
    this.size = 20; 
    this.speed = 3;
    this.spawn();
  }

  display() {
    if (millis() < invincibleUntil) {
      tint(255, 63); //25% transparency
    } else {
      tint(255, 255); 
    }
    image(playerImg, this.x, this.y, this.size, this.size);
  }
  move() {
    
    if (eyeOfSauronActive) { //cant move if active
      return; 
    }
    let newX = this.x;
    let newY = this.y;
    //movement
    if (keyIsDown(LEFT_ARROW) && this.x > 0) {
      newX -= this.speed;
    } else if (keyIsDown(RIGHT_ARROW) && this.x < width - this.size) {
      newX += this.speed;
    }
    if (keyIsDown(UP_ARROW) && this.y > 0) {
      newY -= this.speed;
    } else if (keyIsDown(DOWN_ARROW) && this.y < height - this.size) {
      newY += this.speed;
    }

    if (!this.collidesWithWall(newX, this.y) && !this.collidesWithWall(newX, newY) && !this.collidesWithWall(this.x, newY) && !this.collidesWithWall(newX, newY)) {
      this.x = newX;
      this.y = newY; //updates if there are no collisions with walls
    }
  }
  //collision
  collidesWithWall(x, y) {
    
    //calculates grid indices with helpers
    let left = getCol(x);
    let right = getCol(x + this.size - 1);
    let top = getRow(y);
    let bottom = getRow(y + this.size - 1);
    
    //checks if any grids around player position has # (meaning wall)
    return ( //returns true if collision happens if not false ( or conditions)
      maze[top][left] === '#' ||
      maze[top][right] === '#' ||
      maze[bottom][left] === '#' ||
      maze[bottom][right] === '#'
    );
  }
    //initial spawn
  spawn() {
    this.x = 30;
    this.y = 30;
  }
}

Ring Class: takes care of spawning golden ring as well as checking its collision for player and determining invisibility buff time

const ringSpawnLocation = { row: 10, col: 20 }; //properties
let goldenRingRadius = 10;

function checkRingCollision() {
  if (goldenRing && dist(player.x, player.y, goldenRing.x, goldenRing.y) < goldenRing.size) { //if ring exists and distance is less than rings radius
    //activate sauron and invis buff sequence
    collidedWithRing = true;
    eyeOfSauronActive = true;
    invincibleUntil = millis() + 40000; // make Frodo invincible for 30 seconds
    monsterSpawned = false;
     setTimeout(() => {
      invisibilitySound.play();
    }, 20000);
    if (!sauronSound.isPlaying() && !sauronSoundStarted) {
      sauronSound.play();
      sauronSoundStarted = true;
      goldenRing = null;
    }
  }
}

//creating golden ring
function createGoldenRing(x, y, size) {
  return {
    x,
    y,
    size,
  };
}



//drawing golden ring
function drawGoldenRing() {
  
  image(ringImage, goldenRing.x - goldenRingRadius, goldenRing.y - goldenRingRadius, goldenRingRadius * 2, goldenRingRadius * 2);
}

Game Management Class:  takes care of different game states, main menu state, game win, gameplay, gameover etc. It also displays main menu, instructions and helps with clearing as well as reloading objects and variables upon restart

function startGame() {
  gameStarted = true;
  this.remove(); // remove the Start Game button
  volumeSlider.style('display', 'block'); //displays volume slider

}

function returnToMainMenu() {
  currentScreen = 'mainMenu';
  backButton.hide(); // hide the back button when returning to main menu
}

function showInstructions() {
  currentScreen = 'instructions';
  backButton.show(); // show the back button when instructions are visible
}


function createRestartButton() {
  if (restartButton) {
    restartButton.remove(); // ensure any existing button is removed
  }
  let buttonWidth = 140; 
  let buttonHeight = 35; 
  let buttonX = (width - buttonWidth) / 2; // 
  let buttonY = 200; 
  restartButton = createButton('');
  restartButton.position(buttonX, buttonY);
  restartButton.size(buttonWidth, buttonHeight);
  restartButton.style('background-color', 'transparent');
  restartButton.style('border', 'none'); 
  restartButton.style('cursor', 'pointer');
  restartButton.mousePressed(restartGame);

  // change cursor on hover
  restartButton.mouseOver(() => restartButton.style('cursor', 'pointer'));
}


function gameWin() {
  gameState = 'win';
  backgroundMusic.stop();
  
  //  the game win image
  background(gameWinImg);

  //  text on top of the image
  textSize(31);
  stroke(0);
  strokeWeight(4);
  fill(255); 
  textAlign(CENTER, CENTER);
  text("You've reached the Mount of Doom!", width / 2, height / 2 -50);
  text("Journey Length: " + playTime.toFixed(1) + " seconds", width / 2, height / 2);

  winSound.play();
  if (monsterSound.isPlaying()) {
    monsterSound.stop();
  }
  volumeSlider.style('display', 'none');
  
  createRestartButton();
  noLoop(); //game pause
}


function gameOver() {
  gameState = 'gameOver';
  backgroundMusic.stop();
  
  // the game over image
  background(gameOverImg);
  
  //  text on top of the image
  textSize(45);
  stroke(0);
  strokeWeight(4);
  fill(255); 
  textAlign(CENTER, CENTER);
  text("Game Over!", width / 2, 100);
  text("Survival Time: " + playTime.toFixed(1) + " seconds", width / 2, 150);

  gameoverSound.play();
  if (monsterSound.isPlaying()) {
    monsterSound.stop();
  }
  
  volumeSlider.style('display', 'none');

  createRestartButton();
  noLoop(); // pause
}


function restartGame() {
  // stop sounds
  if (gameoverSound.isPlaying()) {
    gameoverSound.stop();
  }
  if (backgroundMusic.isPlaying()) {
    backgroundMusic.stop();
  }
  if (monsterSound.isPlaying()) {
    monsterSound.stop();
  }
  if (dyingSound.isPlaying()) {  
    dyingSound.stop();
  }
  if (sauronSound.isPlaying()) {
    sauronSound.stop();
  }
  if (invisibilitySound.isPlaying()) {
    invisibilitySound.stop();
  }
  if (winSound.isPlaying()) {
    winSound.stop();
  }

  // remove the restart button if it exists
  if (restartButton) {
    restartButton.remove();
    restartButton = null;
  }

  // reset the game state and variables for a new game
  resetGameState();

  // reset startTime to the current time to restart the timer
  startTime = millis();

  // ensure the game loop is running if it was stopped
  loop();
}



function resetGameState() {
  // reset game flags and variables
  gameStarted = true;
  gameState = 'playing';
  playerHealth = 3;
  playTime = 0;
  monsterSpawned = false;
  collidedWithRing = false;
  goldenRingSpawned = false;
  eyeOfSauronActive = false;
  eyeOfSauronDeactivated = false;
  eyeSize = 15;
  currentLevel = 1;
  
  // reset positions and states of game entities
  player.spawn();
  orcs = []; // clear existing orcs
  generateLevel(); // repopulate the orcs array

  if (volumeSlider) {
    volumeSlider.remove(); // ensure existing slider is removed before creating a new one
  }
  
  //new slider
  volumeSlider = createSlider(0, 1, 1, 0.01); 
  volumeSlider.position(180, 1.5);
  volumeSlider.style('width', '100px');
  volumeSlider.style('color', 'black');
  volumeSlider.style('outline', 'none');
  volumeSlider.style('background', '#white');
  volumeSlider.style('opacity', '0.7');
  volumeSlider.input(() => backgroundMusic.setVolume(volumeSlider.value()));

  // reset the background music volume and play it if not already playing
  backgroundMusic.setVolume(1); // set initial volume
  if (!backgroundMusic.isPlaying()) {
    backgroundMusic.loop();
  }

  // ensure the game loop is running if it was stopped
  loop();
}

Maze class: takes care of maze layout as well as drawing maze. There are two layouts, first one is official game one and second one is for quick testing. It uses helper functions to divide canvas into grids and then draws a maze if it finds # in a grid. It is using a graphic which uses a wall texture and for other places in grids we have grass texture.

let maze = [
  "##########################",
  "#        #   # #   #   # #",
  "# #### # # #   # # # # # #",
  "#   #  ##### ### # # # # #",
  "# #### #     #   # # # # #",
  "#      # # # # ### # # # #",
  "#####    # # # # # # # # #",
  "#   #  ### ### # # # # # #",
  "# # #  # #     # # # #   #",
  "# # # ## ####### ### ### #",
  "# #      #         # # # #",
  "# ################ # # # #",
  "#        #   #     # # # #",
  "# ######## ### ### # # # #",
  "#        # # # # # # # # #",
  "######## # #   # # # # # #",
  "# #    # # # ### # # # # #",
  "#    #   # # #     # # # #",
  "# ## ##### # # ##### # # #",
  "# #  #       #   #   #   #",
  "# #  # ######### # ### ###",
  "# ####   # #   # # # #   #",
  "# #  # # # #   # # # # ###",
  "# ## ### # # ### # # #    ",
  "#        #         #      ",
  "##########################",
];


// let maze = [
//   "                          ",
//   "#        #   # #   #   # #",
//   "# #### # # #   # # # # # #",
//   "#   #  ##### ### # # # # #",
//   "# #### #     #   # # # # #",
//   "#      # # # # ### # # # #",
//   "#####    # # # # # # # # #",
//   "#   #  ### ### # # # # # #",
//   "# # #  # #     # # # #   #",
//   "# # # ## ####### ### ### #",
//   "# #      #         # # # #",
//   "# ################ # # # #",
//   "#        #   #     # # # #",
//   "# ######## ### ### # # # #",
//   "#        # # # # # # # # #",
//   "######## # #   # # # # # #",
//   "# #    # # # ### # # # # #",
//   "#    #   # # #     # # # #",
//   "# ## ##### # # ##### # # #",
//   "# #  #       #   #   #   #",
//   "# #  # ######### # ### ###",
//   "# ####   # #   # # # #   #",
//   "# #  # # # #   # # # # ###",
//   "# ## ### # # ### # # #    ",
//   "#        #         #      ",
//   "##########################",
// ];

// helper functions for row and col 
function getRow(y) {
  return floor(y / 30);
}

function getCol(x) {
  return floor(x / 30);
}

function drawMaze() {
  for (let i = 0; i < maze.length; i++) {
    for (let j = 0; j < maze[i].length; j++) {
      let x = j * 25;
      let y = i * 25;
      if (maze[i][j] === '#' && !eyeOfSauronActive) {
        // Draw wall texture only if Eye of Sauron is not active
        image(wallBuffer, x, y, 25, 25, x, y, 25, 25);
      } else if (drawGrass && maze[i][j] !== '#') {
        // Draw grass texture over the green areas (paths) if drawGrass is true
        // and the current cell is not a wall.
        image(grassBuffer, x, y, 25, 25, x, y, 25, 25);
      }
    }
  }
}

MountDoom Class: creates mount doom, uses a function for tracking and moving monster towards Frodo as well as a function which determines if Frodo is inside mount of doom range.

function moveMonsterTowardsFrodo() {
  let dx = player.x - monsterX;
  let dy = player.y - monsterY;
  let angle = atan2(dy, dx); //angle between monster and player
  monsterX += monsterSpeed * cos(angle);
  monsterY += monsterSpeed * sin(angle);
  //update monster position based on calculated angle
}


class MountOfDoom {
  constructor() {
    this.x = width - 75;
    this.y = height - 95;
    this.size = 75;
  }
}


function createMountOfDoom() {
  return new MountOfDoom();
}


function playerReachedMountOfDoom() {
  return (
    !monsterSpawned && //monster has not spawned and its in bounds
    player.x + player.size > mountOfDoom.x &&
    player.x < mountOfDoom.x + mountOfDoom.size &&
    player.y + player.size > mountOfDoom.y &&
    player.y < mountOfDoom.y + mountOfDoom.size
  );
}

This is Eye of Sauron class:

it takes care of Eye of Sauron animation (used from one of the previous projects). This is drawn using various perlin noise loops. It also has activation and eye increase rate after predetermined time period. (It times well with audio e.g. death = engulfed in darkness).

let orange = 165; // clicking color variable
let size_t = 100; // clicking size variable

function drawEyeOfSauron() {
  background(0, 0, 0, 3);
  push();
  translate(width / 2, height / 2);
  let noiseMax = 5; // fixed value for spikiness
  let alphaValue = 400;

  
  eyeSize += 0.05;

  // outer shape
  stroke(255, 10, 0, alphaValue);
  noFill();
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(10 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(sin(a + phase), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 100, 220) * (eyeSize / 20); // scale based on eyesize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // orange glow for the first outer shape
  fill(255, orange, 0, alphaValue * 0.5); // lower transp
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(8 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(8 * sin(a + phase), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 0, size_t) * (eyeSize / 20); // scale based on eyesize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // second glow
  fill(255, 165, 0, alphaValue * 0.5);
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(10 * cos(a + phase + 1), -1, 1, 0, noiseMax); // different phase
    let yoff = map(10 * sin(a + phase + 1), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 50, 220) * (eyeSize / 20); // scale based on eyesize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // inner pupil black which is a vertical ellipse
  fill(0); // black
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(5 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(5 * sin(a + phase), -1, 1, 0, noiseMax);
    let rx = map(noise(xoff, yoff, zoff), 0, 1, 5, 20) * (eyeSize / 20); // scale based on eyesize
    let ry = map(noise(yoff, xoff, zoff), 0, 1, 50, 120) * (eyeSize / 20); // scale based on eyesize
    let x = rx * cos(a);
    let y = ry * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  zoff += 0.008;
  phase += 0.008;
 
  if (eyeOfSauronActive && sauronSound.isPlaying()) {
    let timeRemaining = sauronSound.duration() - sauronSound.currentTime();
    if (timeRemaining < 0.7) { 
      eyeSize += 50;
    }
  }

  pop();
}

And lastly, the code I am most proud, where everything comes together is my sketch code:

This is where, variables, preload and setup is made. You can see detailed list in the code but in summary it takes care of initializing objects, creating buttons, slider as well as separate graphic for textures.

The next section is draw function which has different if conditions for different states. For example, if game has not started are we in instructions or main menu. We also have additional drawings, game state function, and references to previous classes to make everything initialize and work well together. Getting everything work well together was through multiple hours of trial and error but eventually the experience created was pretty fluid with no significant performance or visual bugs.

//Variables

//Time
let startTime; 
let playTime = 0;
let mountOfDoomTime = 0;

//Objects
let playerImg, orcImg, mountOfDoomImg;
let player, orcs, playerHealth, mountOfDoom;

//Audio

let volumeSlider;
let winSound;
let backgroundMusicStarted = false;

//ring
let goldenRing;
let goldenRingSpawned = false;
let collidedWithRing = false;

//sauron
let eyeSize = 15;
let eyeOfSauronActive = false;
let isSauronSoundLowered = false;
let eyeOfSauronDeactivated = false;
let zoff = 0;
let phase = 0;
let noiseMax = 0;
let sauronSoundStarted = false;

//monster
let monsterImg;
let monsterSpawned = false;
let monsterSpeed = 0.3;
let monsterX, monsterY;
let monsterCheck = false;
let monsterSizeMultiplier = 0.2;

//buff
let invincibleUntil = 0;

//state 
let gameWinImg, gameOverImg;
let gameState = 'playing'; 
let gameStarted = false;

//font
let pixelFont;

//for managing 
let restartButton;
let mainmenu;
let currentScreen = 'mainMenu';
let newBackgroundImg;

//Maze Management Misc
let drawGrass = true;

//Preload

function preload() {
  playerImg = loadImage('frodo.png');
  orcImg = loadImage('orc.png');
  mountOfDoomImg = loadImage('volcano.png');
  backgroundMusic = loadSound('lotr.mp3');
  dyingSound = loadSound('dying.mp3');
  gameoverSound = loadSound('gameoversound.mp3');
  winSound = loadSound('win.mp3');//all the sounds
  ringImage = loadImage('ring.png');
  sauronSound = loadSound("sauron.mp3")
  monsterImg = loadImage('monster.gif');
  invisibilitySound = loadSound('invisible.mp3');
  ringSpawnSound = loadSound('ringspawn.mp3');
  monsterSound = loadSound('monster.mp3');
  gameWinImg = loadImage('game_won.png');
  gameOverImg = loadImage('game_over.png');
  pixelFont = loadFont('alagard.ttf');
  mainmenu = loadImage('mainmenu.png')
  newBackgroundImg = loadImage('instructions.png');
  grassTexture = loadImage('grass.jpeg');
  wallTexture = loadImage('wall.jpg');
  
}

//Safari Bug (audio does not autoplay, not a problem on chromium)
function keyPressed() {
  // start background music when a key is pressed
  if (!backgroundMusicStarted) {
    backgroundMusicStarted = true;
    backgroundMusic.play();
  }
}

//Setup

function setup() {
  textFont(pixelFont); //using external font
  frameRate(60);
  startTime = millis(); //for calculating journey time in the end
  let initialVolume = 1;
  createCanvas(650, 650);
  generateLevel(); // ?
  player = new Player(); //initialize player
  playerHealth = 3; //initialize player health
  window.addEventListener('keydown', function (e) {
    if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
      e.preventDefault(); // prevent default arrow key behavior for safari bug (moving screen)
    }
  });
  
  //monster
  monsterX = width / 2;
  monsterY = height / 2;
 
  mountOfDoom = createMountOfDoom();
  
  //vol slider
  volumeSlider = createSlider(0, 1, initialVolume, 0.01);
  volumeSlider.position(180, 1.5);
  volumeSlider.style('width', '100px');
  volumeSlider.style('color', 'black');
  volumeSlider.style('outline', 'none');
  volumeSlider.style('background', '#white');
  volumeSlider.style('outline', 'none');
  volumeSlider.style('opacity', '0.7');
  volumeSlider.style('transition', 'opacity .2s');


  // mouse over effect
  volumeSlider.mouseOver(() => {
    volumeSlider.style('opacity', '1');
  });

  volumeSlider.mouseOut(() => {
    volumeSlider.style('opacity', '0.7');
  });

  // webkit for browsers
  volumeSlider.style('::-webkit-slider-thumb', 'width: 25px; height: 25px; background: #04AA6D; cursor: pointer;');
  
  volumeSlider.style('display', 'none');

  backgroundMusic.loop();
  
  //start button
  let startButton = createButton(''); //empty because i couldnt get button itself have external font, so it realies on empty button and actual clickable text superimposed on it
  let buttonWidth = 140; 
  let buttonHeight = 35; 
  let buttonX = (width - buttonWidth) / 2; // center the button horizontally
  let buttonY = 175; 
  startButton.position(buttonX, buttonY);
  startButton.size(buttonWidth, buttonHeight);
  startButton.style('background-color', 'transparent');
  startButton.style('border', 'none'); // no border
  startButton.style('cursor', 'pointer');

  // start game on click
  startButton.mousePressed(startGame);
  
  // question, instruciton button
  
  questionMarkButton = createButton('?'); 
  questionMarkButton.position(width/2-45, 15); 
  questionMarkButton.style('background-color', 'transparent');
  questionMarkButton.style('border', 'none');
  questionMarkButton.style('color', '#FFFFFF'); // text color
  questionMarkButton.style('font-size', '50px'); // size 
  questionMarkButton.style('background-color', 'black'); //  background color 
  questionMarkButton.style('color', '#FFFFFF');  //question mark color
  questionMarkButton.style('padding', '7px 35px'); // pading
  questionMarkButton.style('border-radius', '5px'); // rounded corners

  // managing mouse over effect
  questionMarkButton.mousePressed(showInstructions);

  questionMarkButton.mouseOver(() => questionMarkButton.style('color', '#FFC109')); // change color on hover
  questionMarkButton.mouseOut(() => questionMarkButton.style('color', '#FFFFFF')); // revert color on mouse not hovering

  // arrow button 
  backButton = createButton('←'); 
  backButton.position(10, 10); 
  backButton.mousePressed(returnToMainMenu);
  backButton.hide(); // hide it initially
  
  //buffer for creating another canvas instance
  grassBuffer = createGraphics(width, height);
  
  // grass texture with reduced transparency
  grassBuffer.tint(255, 100); // a bit transparent for visuals
  grassBuffer.image(grassTexture, 0, 0, width, height);
  
  // buffer for the wall texture
  wallBuffer = createGraphics(width, height);

  // scale the buffer before drawing the image
  wallBuffer.push(); // save
  wallBuffer.scale(2); // scale up
  wallBuffer.image(wallTexture, 0, 0, width * 2, height * 2); 
  wallBuffer.pop(); // restore
  
}

//Draw

function draw() {
  if (!gameStarted) { //for menu and instructions
    if (currentScreen === 'mainMenu') { //main menu
      
      background(mainmenu); //background image
      //text properties
      textSize(38);
      stroke(0);
      strokeWeight(5);
      textAlign(CENTER, CENTER);
      text('Lord of the Maze', width / 2, 150);

      textSize(24); //start
      let startGameText = "Begin Journey!";
      let startGameWidth = textWidth(startGameText);
      let startGameX = width / 2 - startGameWidth / 2;
      let startGameY = 180 - 2; // y position of text
      let startGameHeight = 24; // height of the text

      // mouse hover detection based on text position and size
      if (mouseX >= startGameX && mouseX <= startGameX + startGameWidth && mouseY >= startGameY && mouseY <= startGameY + startGameHeight) {
        fill("#FFC109"); // change color to indicate hover
      } else {
        fill("#FFFFFF"); // defauult color
      }

      textAlign(CENTER, CENTER);
      text(startGameText, width / 2, 180 + 12); // draw begin jorney text
      questionMarkButton.show();

    } else if (currentScreen === 'instructions') {
      background(newBackgroundImg); // show the instructions background
      fill(255); //  text color
      textSize(20); //  text size
      textAlign(CENTER, CENTER);
      text("Oh valiant traveler, embroiled in a quest most dire: \n to traverse the winding labyrinths of Middle-earth and \n consign the accursed One Ring to the molten depths  of Mount Doom. \n Be forewarned, the path is fraught with peril, \n and the all-seeing Eye of Sauron ever seeks to ensnare thee. \n \nEmploy the sacred Arrow Keys \n to navigate the maze's enigmatic corridors. \n Each stride shall bring thee closer to thy destiny or doom. \n Avoid orcs! Find one ring and reach Mount of Doom", width / 2, 130);
      questionMarkButton.hide();
      
    }
  } else {
      if (gameState === 'playing') {
        questionMarkButton.hide();
      // change background color based on whether the Eye of Sauron has deactivated
      if (!eyeOfSauronActive && !eyeOfSauronDeactivated) {
        background(178, 223, 138); // original color before Eye of Sauron appears
      } else if (eyeOfSauronDeactivated) {
        background(210, 180, 140); // new color after Eye of Sauron disappears
      }
        
        if (!eyeOfSauronActive) {
          image(mountOfDoomImg, mountOfDoom.x, mountOfDoom.y, mountOfDoom.size, mountOfDoom.size);
        }
      
      //more drawing
      drawMaze();
      playTime = (millis() - startTime) / 1000;
      drawUI();
      player.move();
      player.display();

      //stoo invisibility buff sound
      if (millis() >= invincibleUntil && invisibilitySound.isPlaying()) {
        invisibilitySound.stop();
      }

      noTint();
      orcs.forEach(orc => {
        orc.move();
        orc.display();
      });

      checkCollisions();

      if (playerReachedMountOfDoom()) {
        if (!collidedWithRing) {
          // spawn monster in the center only if they still havent collected ring
          monsterSpawned = true;
          monsterCheck = true;

        } else {

          gameWin();
          volumeSlider.remove();
        }
      }

    let newMonsterWidth = monsterImg.width * monsterSizeMultiplier;
    let newMonsterHeight = monsterImg.height * monsterSizeMultiplier;

    // draw the monster 
    if (monsterSpawned) {
      if (!monsterSound.isPlaying()) {
        monsterSound.loop(); 
      }

      moveMonsterTowardsFrodo();

      let newMonsterWidth = monsterImg.width * monsterSizeMultiplier;
      let newMonsterHeight = monsterImg.height * monsterSizeMultiplier;
      image(monsterImg, monsterX, monsterY, newMonsterWidth, newMonsterHeight);
      
      //monster touches frodo looses

      if (dist(player.x, player.y, monsterX, monsterY) < player.size) {
        gameOver();
      }
    } else {
      if (monsterSound.isPlaying()) {
        monsterSound.stop();
      }
    }
    
    //golden ring

    if (goldenRingSpawned && gameState === 'playing' && goldenRing != null) {
      drawGoldenRing();
      checkRingCollision();
    }

      if (orcs.length == 0) {
        currentLevel++;
        generateLevel();
      }
      
       

    //golden ring and specific location
        
      if (!goldenRingSpawned && getRow(player.y) === ringSpawnLocation.row && getCol(player.x) === ringSpawnLocation.col) {
        goldenRing = createGoldenRing(width / 2 + 138, height / 2 - 110, 15);
        goldenRingSpawned = true;
        ringSpawnSound.play(); 
      }


    //eye of sauron and managing music
      if (eyeOfSauronActive ) {
          drawGrass = false; // to not draw grass during eye of sauron animation
          drawEyeOfSauron();
          if (!sauronSound.isPlaying() && sauronSoundStarted) {
            eyeOfSauronActive = false;
            sauronSoundStarted = false;
            eyeOfSauronDeactivated = true; 
            backgroundMusic.setVolume(initialVolume);
          }
        } else {
          drawGrass = true; // resume drawing grass when eye of sauron is not active
        }
    //another game over condition 
      if (playerHealth <= 0) {
        gameOver();
      }
        
        //restart button
        
         if (gameState === 'gameOver' || gameState === 'win') {
    fill(255); 
    textAlign(CENTER, CENTER);
    textSize(24); 
    textFont(pixelFont); 
    text("Restart Game", width / 2, 200); // y position to match the button's
     }


    } 
  }
}


//Collisions


function checkCollisions() {
  if (millis() < invincibleUntil) {
    return; // skip collision check if Frodo is invincible
  }

  orcs.forEach(orc => {
    if ( //orc dimensions
      player.x < orc.x + orc.size &&
      player.x + player.size > orc.x &&
      player.y < orc.y + orc.size &&
      player.y + player.size > orc.y
    ) {
      playerHealth--; //substract player health
      player.spawn(); //respawn
      dyingSound.play(); //play death sound
    }
  });
}

//Helper Functions


function getRow(y) { //convert y into row index (25 units for maze)
  return floor(y / 25);
}

function getCol(x) { //convert x into column index (25 units for maze)
  return floor(x / 25);
}

Conclusion and Future Considerations

In the end, I am very happy with how things turned out. Despite numerous problems and many more solutions and trials and errors, I developed a project that stands strong in almost every department we studied – there is a bit of everything. I hope it did at least some level of justice to inspiration source and achieved a design aesthetic that is consistent throughout. The scope of the project, will initially seemed simple, actually turned out to be much more complex when I started working on it, as there are lots of moving elements (literally and figuratively). This does not mean that it’s perfect of course. There are some improvements and suggestions to be made. For example, I could potentially add more monster types and more interactions and hidden surprises. The scale of the maze could be larger as well. Additionally, this is only one part of Hero’s journey. Story could be extended to what happens after reaching mount of doom. This calls for additional level. Moreover, the maze is fixed and static. It would be interesting to try procedural maze generation technique, so it is a unique maze each time game is loaded. On a final note, I hope you enjoy my game and I will definitely expand it in the future.

 

Week 5 Reading Response – Dachi Tarughishvili

The article talks about computer vision which is a field of computer science responsible for designing algorithms that enable computers to provide some level of analysis for digital content. The article mainly talks about new ways of using computer vision, in digital media, art, and more which are nowadays accessible to a much wider range of consumers. 

There were many points that I found interesting in the article, starting from the methodologies behind vision algorithms to social implications. Myron Krueger’s legendary Videoplace reminded me of a project I did in last year’s class virtual body performance. I created an environment in Unity similar to how Videoplace used human canvas and allowed interaction with elements through movement. I also used bodily movements to signify unlocking chakras and connecting to the outer world through physical movements reflected in VR using body capture technology. While methodologies are different, at the core, they are both interactive performances that require a combination of computer technology, human creativity, and physical involvement.

The second and far more interesting point involves ethical considerations coming from the Golden Gate Bridge incident. How morally acceptable is it for us to record people’s final moments for all the world to see? And all this happening without consent is the most problematic aspect. In this case, there is a fine line between artistic expression and socially acceptable expectations. In the future, this line will be blurred further as various cameras around us start to capture more and more data, with better, more sophisticated algorithms. Soon, an immense amount of data concerning our daily routines, choices, feelings, and other personal information will be collected and stored in massive databases. This information will include data on our facial expressions, movements, and more. Unfortunately, there is potential for this data to be exploited by corrupt governments or large technological conglomerates.

On a final note, I agree that computer vision technology has enormous potential for innovation and creativity, but it must be used ethically and responsibly to ensure privacy/ethical concerns are addressed going forward into the future.

Week 5 – Midterm Progress – Lord of the Maze – Dachi Tarughishvili

Concept:

My midterm will be a game titled Lord of the Maze which is inspired by epic fantasy novel called Lord of the Rings which my previous project Eye of Sauron was based on. This time around I want to make a maze style game where main character Frodo has to make his way out of the maze and reach Mount Doom (volcano in Mordor where the ring is forged and place where it can be destroyed). In addition to the maze, orcs are roaming along different pathways which you should avoid. Coming in direct contact with an orc will decrease your health. If you loose all three health points, you lose the game. If you reach mount of doom in time, you win the game, and image gets displayed. Some of the features I am considering adding are: time counter, sound effects, more orcs, more movement patterns, Eye of Sauron additional level before reaching mount of doom and destroying the ring.

Design:

Maze Generator | Graphic design inspiration, Maze, Design inspiration

These are some of the basic design elements behind my midterm project as it is going to be top down, 2D 8 bit pixel art-style maze game. I am also going to add iconic soundtracks from LOTR to enhance the atmosphere. I am also thinking of other obstacles or objectives along the way like additional monster or a ring. Additionally, I want a spell casting ability design of which might either be animated or might opt for simpler integration with visual changes P5js offers. The maze itself is gonna be fixed for ease of implementation, and collision detection, however, I could possibly pursue more generative design in the future.

Classes will most likely be: UI, Ring, Orc, Mount of Doom, Game Win, Game Over conditions and more.

Challenges:

Challenges will most likely come from collision detection. The maze logic should be properly implemented so that Frodo does not go through walls and does not break player immersion.

Additionally, it will be quite difficult to make all objects work together as they have to abide different rules. I don’t think the movement itself will be an issue as most monsters should have predefined path. Integrating eye of Sauron animation from project before both programmatically and conceptually might be challenging.

Risk Prevention:

To maximize the efficiency of implementation, I am going to approach the task step by step. First, I am going to design the maze and check basic collisions for the character.

After that I am going to add orcs and make them move on predefined paths. I will then implement Ui Elements since games core logic works successfully and I can only build up from there.

Additional logic concerning the eye, reaching mount of doom, additional obstacle, the ring, and potential power will be built each one at a time.

Eventually, I am going to try to polish everything together, improve my comments and foundation and possibly work on graphics and transitions.

As far as audio is concerned, each new element will be accompanied by relevant audio file and thats how they are going to be added as well consequently.

 

 

Asciilumination – Assignment 4 – Dachi Tarughishvili

Passing by Interactive Media Lab, I always noticed those TVs that used webcams to track our movement and demonstrate interesting visualization using predefined symbols. I always wondered how it actually worked. So for this data visulization assignment I wanted to recreate it and perhaps add some more adjustments. I followed Coding Train’s coding challenge tutorial regarding ASCII Text images where most of my code comes from.
The basic mechanism behind this program is to map the pixel brightness values which are calculated by dividing their average RGB values and mapping it to characters from density character strings. In this case, I am using ‘ÑYUAD876543210?!abc;:+=-,._’; since initial symbols take up more density and show contrast better but it can realistically be anything. Super bright parts of the image have no space value at all. Additionally, there is no Canvas, and this is direct html implementation with little bit of CSS. I also added a slider for density value which adjusts number of spaces that are added to the string, which acts similiar to how contrast slider would act in photo editing app. If uyou think about it, thats actually whats happening. There are more spaces for wider value of brighter pixels. There is also a Toggle Color button on top left, which assigns the symbols colors based on initial pixel value. This is done per frame basis.
To ensure that you see full working project, make sure to follow P5 link, since this website does not correctly display it and also you need camera permissions anyway since it takes the video of your webcam.
Here is perhaps the most important code out of entire project:

 //loop to iterate over pixels
  for (let j = 0; j < video.height; j++) { //iterates over rows (height)
    for (let i = 0; i < video.width; i++) { //iterates over columns (width)
      const pixelIndex = (i + j * video.width) * 4; 
      //calculates index of pixel in videos pixel array based on its x and y cordinates (i and j). basically vertical and horizontal displacement. Video width indicates how many pixels are in each row. r, g b, a so we multiply by 4 since pixel takes up 4 spaces in array
      const r = video.pixels[pixelIndex];
      const g = video.pixels[pixelIndex + 1];
      const b = video.pixels[pixelIndex + 2];
      const avg = (r + g + b) / 3;
      const len = adjustedDensity.length;//to later map brightness value
      
      const charIndex = floor(map(avg, 0, 255, 0, len)); 
      
      //maps avg value from 0 to 255 to 0 to len. floor is used for rounding
      
      const c = adjustedDensity.charAt(charIndex);
      
      //brighter pixel = higher char index lower density
      
      const charColor = `rgb(${r},${g},${b})`; 
      //its a template literal, javascript uses it for embedding expressions within the string
      
      if (c == " ") {
        asciiImage += "&nbsp;";
      } else if (check == true) {
         asciiImage += `<span style="color:${charColor};">${c}</span>`; 
      } else {
        asciiImage += c; //adds c to our image
      }
      
      //span element is inline container in hztml used ot apply styles without line break
      //if our c is empty by mapping, correspond that to true space (html does not conventionally display empty spaces) 
      
    }
    asciiImage += '<br/>'; //line break to start fresh rows
  }
  asciiDiv.html(asciiImage); //sets html content of ascidiv to our finalized asciimage, by continously drawing we update div content and thus motion is live
}

The code is fully commented but the general idea is to find out r g b values for individual pixels. After that, we find average value which corresponds to our brightness. We map our string index to brightness values. Therefore we have a constant C which is a character for every-frame and we add it to our frame. If color is on, then its color is modified.

In the future, I could work on optimizing since. Refreshing html elements so many times, especially when color is applied is very taxing on CPU and the process becomes laggy. Lowering frame-rate does not help much. Additionally, I could add more adjustmenets and variables, such as video size, font options and more. The latter is especially interesting since the font I am using has same horizontal length. Variable font would be harder to implement. Overall I am very glad with how this project turned out and would like to work on it more in the future.

Reading Reflection Week 4 – Dachi Tarughishvili

Don Norman, author of “Design of Everyday Things” brings various examples and concepts when it comes to technology and challenges that arise with designing devices we use in our everyday lives. The thing that stood out to me the most was his example of a watch with four buttons. The whole paragraph talks about how it makes the user experience less enjoyable since now the watch is not merely about checking time but also providing users with additional functions. While the premise of a more feature-packed piece of technology is quite optimistic, the reality of it only confuses the vast majority of users as it takes away from its main function. Expanded this to today, this remains true. I personally own a smartwatch. Initially, I thought it would be very useful and offer numerous features. While it does all that, after the initial novelty wore off, I saw why so many people dislike them. There is no disagreement that some of these features are quite useful: instant notifications from your phone, the ability to call, reply to messages, check different apps, and so on. However, we already have peace of technology that provides all that and the purpose of the watch is redirected to a compromised clone of such device. Small screen, limited performance, difficulty interacting with UI and small buttons, and abundance of features I will probably never use, are the same sort of challenges Norman talked about. The fact that I have to charge it every day to keep it going puts a huge barrier to usability.
At the end of the day, a watch should serve one function, to display time at a quick glance. How many of us, have used our smartphones to do that even though we ourselves have digital watches on our wrists? We are sacrificing user-friendly design for the sake of complexity. It is a product that tries to be sophisticated – a jack of all trades but never excelling at anything in particular. Even when it comes to displaying time, the options and different layouts get overwhelming. This design ignores fundamental elements of good design which are simplicity, clarity, and ease of use. And what if something goes wrong? It is not a simple repair. It is attractive but not that affordable. Do I regret my purchase? Not really. At least I used it for my homework reflection.

Reading Reflection Week 3 – Dachi Tarughishvili

“The Art of Interactive Design” by Chris Crawford goes in-depth refining a buzzword – interactivity – that we might be using often without understanding all the intricacies of the interface that allows users to design dynamic interactions for the users. According to the author, it’s not just an additional layer of programming but its core aspect. This has also been true in my experience since I have worked on several projects and oftentimes it’s the interactive layer that makes a difference between a solid or lacking experience. For example, making everything user-friendly, and easy to understand but also dynamic enough so that the user does not get bored of repeating the same action again and again.
He makes a clear divide between interaction and reaction and how the cyclic nature of having two systems defines if something is interactable or not. While I understand his idea of having two actors, I don’t think that the branch example is only about having one actor. Even though it’s an inanimate object, it still plays a major role. The better distinction is that the branch while being an actor, is not necessarily something that can modify its behavior based on our (first actor’s) actions.
Nevertheless, in the following paragraphs, the author gets more concrete in his definition and I have to agree, that having two living organisms interact is much more authentic than any kind of computer interaction we are trying to imitate in real life.
The author further continues to define different levels of interaction and how it is a variable that might have different strengths based on how many components it has and how advanced they are (ability to think, speak, listen, etc). I would argue, however, that it is important to define individual aspects, since while something may be interactive based on all those definitions, a user (who is good at listening, speaking, and thinking) might still find something interactive to be lackluster based on their personal experience. For example, imagine an app that teaches you some skills. On paper it is quite interactive, the user listens, speaks, and inputs answers after deliberate thinking. The app, in turn, responds, speaks when needed, and analyzes answers. However, if the user is already fluent in such skills, this interaction component will seem trivial and more of a hassle unless the app is designed to be tailored to their fields of interest or their behavioral patterns.
I agree with his book example, (there is more of a reaction than interaction). However, some movies can indeed be interactive. For example, Black Mirror: Bandersnatch is a 2018 movie where users can make a choice based on which different scenarios will play out. Even here though, you can argue that this is not true interaction since those scenes have already been pre-shot and there is nothing an individual can do to change the script of those scenes.
His final consensus lies in differentiating user experience designer versus interactivity designer. The latter is less concerned about technical aspects but more about how a function makes the user feel. As such, there is the integration of “form with the function”.
All in all, the author was very forward-looking with his statements. Crawford emphasizes its core role in user experience, distinguishing between interaction and reaction. The science of interactivity has advanced a lot after writing this book, especially with the rise of VR where new definitions can be formed every day based on discoveries and new ways to create immersive experiences. Ultimately, his work serves as a good foundation in this ever-evolving field.

Eye of Sauron – Dachi Tarughishvili – Assignment 3

While looking for ideas for generative art I stumbled upon coding train’s playlist by Daniel Shiffman. It was super interesting to see just how many things you can create using simple idea of randomness and how you add different variables to generate seemingly organic patterns. The last video in the playlist was Polar Perlin Noise loops which intrigued me the most. I followed the tutorial and executed the default code which I started messing with to get some interesting results. For example, I changed z_offset and phase values to make the shape spin faster or with greater amplitude, additionally there is a slider to increase the randomness which visually translates to increasing speed. Once I saw the outline I was interesting in not just the changing of the shape but what would happen if background did not override it every time? That way a figure would slowly come to life as we let the code play out. Adding transparency to that aspect made the animation seem smoother and more organic. I changed the color to red and saw how it morphed into circle with spikes after changing amplitude to 10*(cos a + phase). It instantly reminded me of Eye of the Sauron from the Lord of the Rings. Thus I had a picture in mind and a rough plan to execute it. I added more shapes operated by same Perlin noise logic but with different parameters. For example, different phase, color, or shape entirely (latter being very important for the black vertical pupil in the middle of the eye). I then added arcs to imitate the Dark Tower.

I decided to change color (transparency) of the eye, as well as increase its shape if the volume is loud enough. After 6.8 seconds, the camera also begins to work and 0.4 seconds before audio ends, the inner pupil starts to expand. There is also transparency aspect (commented in code) which makes eye more transparent as it comes closer to the subject, the camera overlay is also semitransparent which gives it sort of slow motion effect using tint function.

I followed articles on web to get the capture feed. I had some difficulty with getting camera to work in sync with audio but in the end, simple boolean checks did the trick. Values for audio were just trial and error, as it was for eyesize scaling and volume levels. I am really happy with how it turned out, especially how the audio dialogue matches what is actually happening on screen. (Camera = seen, and death = engulfed in darkness by expanding inner pupil gradually).

I changed few colors and parameters to make it look like the source material and in the end got exactly what I wanted. Perhaps, this is most impressive part of whole code because this is where perlin noise eye animation takes space:

// draw outer shape
  stroke(255, 10, 0, alphaValue);
  noFill();
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(10 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(sin(a + phase), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 100, 220) * (eyeSize / 20); // scale based on eyeSize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // orange glow for the first outer shape
  fill(255, orange, 0, alphaValue * 0.5); // lower transparency
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(8 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(8 * sin(a + phase), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 0, size_t) * (eyeSize / 20); // Scale based on eyeSize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // second glow
  fill(255, 165, 0, alphaValue * 0.5);
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(10 * cos(a + phase + 1), -1, 1, 0, noiseMax); // different phase
    let yoff = map(10 * sin(a + phase + 1), -1, 1, 0, noiseMax);
    let r = map(noise(xoff, yoff, zoff), 0, 1, 50, 220) * (eyeSize / 20); // Scale based on eyeSize
    let x = r * cos(a);
    let y = r * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // inner pupil black which is a vertical ellipse
  fill(0); // black
  beginShape();
  for (let a = 0; a < TWO_PI; a += 0.1) {
    let xoff = map(5 * cos(a + phase), -1, 1, 0, noiseMax);
    let yoff = map(5 * sin(a + phase), -1, 1, 0, noiseMax);
    let rx = map(noise(xoff, yoff, zoff), 0, 1, 5, 20) * (eyeSize / 20); // Scale based on eyeSize
    let ry = map(noise(yoff, xoff, zoff), 0, 1, 50, 120) * (eyeSize / 20); // Scale based on eyeSize
    let x = rx * cos(a);
    let y = ry * sin(a);
    vertex(x, y);
  }
  endShape(CLOSE);

  // update zoff and phase
  zoff += 0.008;
  phase += 0.008;

All in all, I had lots of fun working on this project and I am very happy with the results. Hope you like it too! Here is the final version: (I would highly suggest opening it on actual browser and giving it camera/microphone permissions for the full animation- https://editor.p5js.org/dt2307/full/krd4mZZqJ)

 

 

Week 2 Reading Response – Dachi Tarughishvili

Casey Reas is the inventor of Processing (software which I often used in my Freshman year of university) and is well known for many projects in digital illustrations. In Eyeo2012 he talked about chance operations which I found very intriguing and interesting for several reasons. First of all, his talk about random systems with seemingly random parameters which after some time tend to order and become homogenous (Process 18) is very much similar to what I did for assignment 2, where I used random functions while ending up with clear shapes depending on different parameters after many iterations.
His work based on “Tissue Work” highlights how small changes in parameters largely influence the paths his figures follow. I also found this true when modifying variables of my project which completely changed the shape. More surprisingly, the way the change worked started making sense in a nonmathematical context as well. Without thinking about the equations I could visually deduce how one parameter (percent) influences the resolution of the shape and the other (n) influences how it spreads.
I found it inspiring how many fields can random chance operations be used in physics, music, and obviously art. One might think how all these fields are related by underlying mathematical equations and might even explain how our entire universe works.
Seeing the random number book got me thinking about how if anything is truly random. As a student who studies computer science, we have often been told how you can’t program true randomness. I started wondering if we even need true randomness as controlled randomness helps us in many ways already. Be it encryption, algorithms, and in this case creating art.
The demo regarding triggering our imagination was truly fascinating, as it got me thinking about how our brains try to declutter chaos and try to do their best to make sense of what we see based on our preconceived notions.
I think that Casey Reas’s presentation and his personal experience show us how we might find meaning and beauty in the most unexpected (random) places which in turn should inspire us to look for order in chaos in both our fields and our personal lives.

Fractal Chaos – Dachi Tarughishvili – Assignment 2

I remember watching video by numberphile called Chaos Game years ago which I found very intriguing.  I wanted to create a generative art illustration around that topic something that would involve Fractals and creating order out of randomness. Fortunately, I found Daniel Shiffman’s video regarding Chaos Game where I followed his tutorial for the initial version of the project which draws Sierpinski triangle.

Even this simple configuration of Chaos game is surprisingly beautiful and very interesting as we have created triangle shaped forms simply by drawing points halfway to one of the three random points that is randomly created and then selected.

Next step was to add more complexity to the project, thereby adding more seed points and refactoring whole thing to make changing variables for future reference more dynamic. Tutorial was very helpful in achieving this. To be more precise, I had two values which I could dynamically change: percent (the distance to a randomly chosen point where new point has to be placed at) and const n which is basically number of seed points.

I could make a very lengthy post talking about each aspect of the code but instead I want to highlight the code I am most proud of and rest I will either explain in class or you can see through embedded comments of P5 sketch.

for (let i = 0; i < 200; i++) {
    
    strokeWeight(1);
    stroke(255, 255, 255, 200);
    
    let neighborIndex;
    do {
      neighborIndex = floor(random(points.length)); //from array
    } while (neighborIndex === previousVertexIndex || neighborIndex === (previousVertexIndex + 1) % points.length || neighborIndex === (previousVertexIndex - 1 + points.length) % points.length);
    let next = points[neighborIndex];

    current.x = lerp(current.x, next.x, percent);
    current.y = lerp(current.y, next.y, percent);
    
    point(current.x, current.y);
    
    previousVertexIndex = neighborIndex;
  }
}

This is an additional condition I added to make it so that newly chosen point can’t be the same as the old point or a neighboring point from the polygon. We are using LERP (linear interpolation) for updating position.

In the future, I could possibly experiment with more shapes and more drawings and users are also welcome to modify the code and play with it. Perhaps, adding a slider would make things much more fun to play with parameters. Additionally introducing color, or an interactive element users can play with could potentially be more captivating. For now, I am including the project with parameters of the shape I liked the most, but we can try other ones in class. I really liked making this project and I hope you like it too.