Code Sketch


Siddhi sabale
By: Mhalsakant School
Category: Programming
cleari()
originBottomLeft()

// ============================================================
// ISRO MARS MISSION - WORKING ROBOT EDITION
// Scanning, Collecting Samples, Moon, Vikram Lander & Indian Flag
// ============================================================

var robotX = 220.0 //> var robotX: Double = 220.0
var robotY = 145.0 //> var robotY: Double = 145.0
var robotWalk = 0.0 //> var robotWalk: Double = 0.0
var robotBob = 0.0 //> var robotBob: Double = 0.0
var robotGlow = 0.0 //> var robotGlow: Double = 0.0
var robotHeadTurn = 0.0 //> var robotHeadTurn: Double = 0.0
var robotArmWave = 0.0 //> var robotArmWave: Double = 0.0
var robotTime = 0.0 //> var robotTime: Double = 0.0
var robotFacing = 1.0 //> var robotFacing: Double = 1.0

// ------------------------------------------------------------
// Task / Work Logic (??? ???????? ??????)
// 0: Walk, 1: Scan, 2: Collect
// ------------------------------------------------------------
var robotState = 0  //> var robotState: Int = 0
var stateTimer = 0.0 //> var stateTimer: Double = 0.0
var scanAngle = 0.0 //> var scanAngle: Double = 0.0
var armBend = 0.0 //> var armBend: Double = 0.0

// ------------------------------------------------------------
// Meteor / Shooting Star Variables
// ------------------------------------------------------------
var meteorX = cwidth + 50.0 //> var meteorX: Double = 983.0
var meteorY = cheight - 50.0 //> var meteorY: Double = 586.0
var meteorActive = false //> var meteorActive: Boolean = false
var meteorTimer = 0.0 //> var meteorTimer: Double = 0.0

// ------------------------------------------------------------
// Martian Footprints Array
// ------------------------------------------------------------
val maxFootprints = 16 //> val maxFootprints: Int = 16
val fpX = Array.fill(maxFootprints)(-100.0) //> val fpX: Array[Double] = Array(-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0)
val fpY = Array.fill(maxFootprints)(140.0) //> val fpY: Array[Double] = Array(140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0, 140.0)
var fpIndex = 0 //> var fpIndex: Int = 0
var lastStepX = robotX //> var lastStepX: Double = 220.0

// ------------------------------------------------------------
// Background Dust & Space Particles
// ------------------------------------------------------------
val particleCount = 70 //> val particleCount: Int = 70
val particleX = Array.fill(particleCount)(randomDouble(0, cwidth)) //> val particleX: Array[Double] = Array(478.11775863389795, 694.3791370989925, 802.9396943127675, 855.1733921637399, 603.8970757421289, 517.5934476658514, 39.56127371996218, 155.5957524851566, 240.61612473926053, 149.78382799585117, 650.8487934483676, 464.4411413691633, 81.812048799087, 82.51096771054041, 52.740621435642595, 304.2073607306411, 42.83027228099717, 349.4110745317351, 720.5932962376811, 547.8881470457093, 668.5956589025639, 351.8359276789348, 65.70259524422477, 884.9324373597506, 925.8245098888092, 176.26725215663745, 903.7117212293451, 204.54163748412938, 96.3175054774039, 264.226028733711, 560.685854513917, 818.8873160751805, 846.8532051175182, 910.2845860092001, 502.3404131158318, 435.6720503562793, 638.9886814702015, 243.83354504669114, 132.02424479139665, 916.953784424772...
val particleY = Array.fill(particleCount)(randomDouble(145, cheight)) //> val particleY: Array[Double] = Array(319.66238187583394, 609.1435291094774, 454.4048417850193, 259.24632551617265, 565.7202980205559, 433.6686992593609, 249.70814839226335, 417.18591409419395, 439.7423049681038, 149.05256699053874, 261.3079881714854, 503.25842346605026, 381.4712187012723, 378.19884389635047, 372.2600502867654, 371.4304891979601, 346.34670851459646, 245.59217192236207, 461.7270757010179, 152.48249017588117, 419.59495645912784, 215.68706255287952, 457.4691783247651, 543.0725898827136, 393.9413073921612, 442.7446688973726, 629.9310953233237, 304.0780322144692, 230.24831312060013, 226.847290289128, 412.9180271913911, 518.0348836830178, 198.40795912660758, 178.08890368389345, 250.54929096723768, 364.3126676380116, 439.93670950678097, 540.0525698581234, 383.47149450035255, 36...
val particleSpeed = Array.fill(particleCount)(randomDouble(0.15, 0.6)) //> val particleSpeed: Array[Double] = Array(0.342608962905256, 0.18775006119326795, 0.3851182316932292, 0.5287425244313773, 0.5517709721584296, 0.37800043404811856, 0.1892715699079466, 0.389984258080623, 0.16534817848350555, 0.5553119838524545, 0.5965239716336354, 0.29336688486652207, 0.24175833758976945, 0.23072125326245174, 0.22558154258803537, 0.2965904390034177, 0.551442449933913, 0.5785523031052398, 0.552126905382487, 0.2884203230969745, 0.3544095594629118, 0.15277366907568304, 0.28423436064495755, 0.27933603677785745, 0.41336630376427974, 0.23654918190540036, 0.4998413717664967, 0.3724815627335132, 0.36091408313264195, 0.18210111149502942, 0.5163463405180908, 0.5511616842061061, 0.36161132873740576, 0.46602133073386853, 0.3292483455666179, 0.16292725194427948, 0.5835752224010691, 0.1...

// ------------------------------------------------------------
// Robot Colors
// ------------------------------------------------------------
val whiteRobot = cm.rgb(245, 248, 250) //> val whiteRobot: net.kogics.kojo.doodle.Color = RGBA(UnsignedByte(117),UnsignedByte(120),UnsignedByte(122),Normalized(1.0))
val whiteShadow = cm.rgb(190, 200, 205) //> val whiteShadow: net.kogics.kojo.doodle.Color = RGBA(UnsignedByte(62),UnsignedByte(72),UnsignedByte(77),Normalized(1.0))
val greenMain = cm.rgb(0, 210, 80) //> val greenMain: net.kogics.kojo.doodle.Color = RGBA(UnsignedByte(-128),UnsignedByte(82),UnsignedByte(-48),Normalized(1.0))
val greenDark = cm.rgb(0, 105, 45) //> val greenDark: net.kogics.kojo.doodle.Color = RGBA(UnsignedByte(-128),UnsignedByte(-23),UnsignedByte(-83),Normalized(1.0))
val greenGlow = cm.rgb(40, 255, 120) //> val greenGlow: net.kogics.kojo.doodle.Color = RGBA(UnsignedByte(-88),UnsignedByte(127),UnsignedByte(-8),Normalized(1.0))
val blackScreen = cm.rgb(5, 8, 18) //> val blackScreen: net.kogics.kojo.doodle.Color = RGBA(UnsignedByte(-123),UnsignedByte(-120),UnsignedByte(-110),Normalized(1.0))

// ------------------------------------------------------------
// UPDATE LOGIC (??? ???????? ?????)
// ------------------------------------------------------------
def updateRobot() {
  robotTime += 0.018
  robotGlow += 0.08
  stateTimer += 0.018

  // Shooting Star Logic
  meteorTimer += 0.018
  if (!meteorActive && meteorTimer > 3.5) {
    meteorActive = true
    meteorX = randomDouble(cwidth * 0.4, cwidth + 50)
    meteorY = randomDouble(cheight - 80, cheight + 20)
    meteorTimer = 0.0
  }

  if (meteorActive) {
    meteorX -= 9.0
    meteorY -= 5.5
    if (meteorY < 180 || meteorX < -50) {
      meteorActive = false
    }
  }

  // ----------------------------------------------------------
  // State Machine: ????? -> ????? ???? -> ??? ?????
  // ----------------------------------------------------------
  if (robotState == 0) { // WALKING
    robotWalk += 0.09
    robotX += 0.5 * robotFacing
    robotBob = math.sin(robotWalk * 2.0) * 3.5
    robotArmWave = math.sin(robotWalk) * 22.0
    robotHeadTurn = math.sin(robotTime * 1.5) * 3.0

    // Footprints
    if (math.abs(robotX - lastStepX) > 22.0) {
      fpX(fpIndex) = robotX - (15.0 * robotFacing)
      fpY(fpIndex) = 142.0 + randomDouble(-2, 2)
      fpIndex = (fpIndex + 1) % maxFootprints
      lastStepX = robotX
    }

    // 5 ????? ??????????? ????? ??? ???? ???
    if (stateTimer > 5.0) {
      robotState = 1
      stateTimer = 0.0
    }

    if (robotX > cwidth - 100) robotFacing = -1.0
    if (robotX < 100) robotFacing = 1.0

  } else if (robotState == 1) { // SCANNING ROCK (????????)
    robotBob = math.sin(robotTime * 2.0) * 1.5
    robotArmWave = 10.0
    scanAngle = math.sin(robotTime * 4.0) * 25.0
    robotHeadTurn = -10.0 * robotFacing

    // 4 ????? ????? ?????????? ?????? ????
    if (stateTimer > 4.0) {
      robotState = 2
      stateTimer = 0.0
    }

  } else if (robotState == 2) { // COLLECTING SAMPLE (??? ???? ????)
    robotBob = -8.0 // ???? ?????
    armBend = math.sin(robotTime * 3.0) * 15.0
    robotHeadTurn = -15.0 * robotFacing

    // 4 ????? ???? ?????????? ?????? ????? ???? ???
    if (stateTimer > 4.0) {
      robotState = 0
      stateTimer = 0.0
    }
  }

  // Floating Dust Particles
  for (i <- 0 until particleCount) {
    particleY(i) += particleSpeed(i)
    if (particleY(i) > cheight) {
      particleY(i) = 145
      particleX(i) = randomDouble(0, cwidth)
    }
  }
} //> def updateRobot(): Unit

// ============================================================
// VIKRAM LANDER
// ============================================================
def drawVikramLander(canvas: CanvasDraw, lx: Double, ly: Double) {
  canvas.stroke(180, 180, 190)
  canvas.strokeWeight(3.0)
  canvas.line(lx - 25, ly, lx - 45, ly - 22)
  canvas.line(lx + 25, ly, lx + 45, ly - 22)
  canvas.line(lx - 12, ly, lx - 20, ly - 22)
  canvas.line(lx + 12, ly, lx + 20, ly - 22)

  canvas.fill(120, 120, 130)
  canvas.noStroke()
  canvas.ellipse(lx - 45, ly - 22, 12, 5)
  canvas.ellipse(lx + 45, ly - 22, 12, 5)

  canvas.fill(220, 165, 30)
  canvas.stroke(180, 130, 20)
  canvas.strokeWeight(1.5)
  canvas.rect(lx - 28, ly, 56, 32)

  canvas.fill(190, 140, 20)
  canvas.rect(lx - 20, ly + 32, 40, 14)

  canvas.fill(20, 70, 160)
  canvas.stroke(40, 100, 200)
  canvas.strokeWeight(1)
  canvas.rect(lx - 48, ly + 6, 18, 26)
  canvas.rect(lx + 30, ly + 6, 18, 26)

  canvas.fill(210, 215, 220)
  canvas.noStroke()
  canvas.ellipse(lx, ly + 52, 16, 10)
  canvas.stroke(200, 200, 210)
  canvas.strokeWeight(2)
  canvas.line(lx, ly + 46, lx, ly + 52)

  canvas.fill(255, 153, 51); canvas.noStroke(); canvas.rect(lx - 10, ly + 20, 20, 3)
  canvas.fill(255, 255, 255); canvas.rect(lx - 10, ly + 17, 20, 3)
  canvas.fill(19, 136, 8); canvas.rect(lx - 10, ly + 14, 20, 3)
} //> def drawVikramLander(canvas: builtins.CanvasDraw, lx: Double, ly: Double): Unit

// ============================================================
// INDIAN TRICOLOR FLAG
// ============================================================
def drawIndianFlag(canvas: CanvasDraw, fx: Double, fy: Double) {
  canvas.stroke(210, 215, 220)
  canvas.strokeWeight(3.5)
  canvas.line(fx, fy, fx, fy + 80)

  canvas.fill(255, 215, 0)
  canvas.noStroke()
  canvas.ellipse(fx, fy + 81, 7, 7)

  val wave = math.sin(robotTime * 4.0) * 3.0

  canvas.fill(255, 153, 51)
  canvas.rect(fx, fy + 58 + wave, 44, 11)

  canvas.fill(255, 255, 255)
  canvas.rect(fx, fy + 47 + wave, 44, 11)

  canvas.stroke(0, 0, 128)
  canvas.strokeWeight(1.2)
  canvas.ellipse(fx + 22, fy + 52.5 + wave, 7.5, 7.5)

  canvas.fill(19, 136, 8)
  canvas.noStroke()
  canvas.rect(fx, fy + 36 + wave, 44, 11)
} //> def drawIndianFlag(canvas: builtins.CanvasDraw, fx: Double, fy: Double): Unit

// ============================================================
// BACKGROUND & MARS ROCK FOR WORK
// ============================================================
def drawRobotBackground(canvas: CanvasDraw) {
  canvas.fill(20, 5, 12)
  canvas.noStroke()
  canvas.rect(0, 0, cwidth, cheight)

  canvas.fill(80, 25, 20)
  canvas.rect(0, 0, cwidth, 180)

  // Stars
  canvas.fill(255, 230, 220)
  for (i <- 0 until 40) {
    val sx = (i * 37) % cwidth
    val sy = 160 + ((i * 53) % (cheight - 160))
    canvas.ellipse(sx, sy, 2, 2)
  }

  // Shooting Star
  if (meteorActive) {
    canvas.stroke(255, 240, 180, 220)
    canvas.strokeWeight(2.5)
    canvas.line(meteorX, meteorY, meteorX + 35, meteorY + 22)

    canvas.fill(255, 255, 255)
    canvas.noStroke()
    canvas.ellipse(meteorX, meteorY, 4, 4)
  }

  // Moon
  val mx = cwidth - 130
  val my = cheight - 100

  canvas.fill(255, 230, 200, 30); canvas.ellipse(mx, my, 110, 110)
  canvas.fill(255, 240, 220, 60); canvas.ellipse(mx, my, 85, 85)
  canvas.fill(240, 235, 220); canvas.ellipse(mx, my, 65, 65)

  canvas.fill(200, 195, 180)
  canvas.ellipse(mx - 10, my + 8, 14, 12)
  canvas.ellipse(mx + 12, my - 10, 18, 15)
  canvas.ellipse(mx + 5, my + 15, 10, 10)

  // Earth
  canvas.fill(70, 130, 200, 180)
  canvas.ellipse(100, cheight - 70, 20, 20)

  // Distant Mountains
  canvas.fill(90, 30, 25)
  canvas.ellipse(100, 145, 320, 120)
  canvas.ellipse(350, 145, 450, 160)
  canvas.ellipse(650, 145, 380, 140)

  drawVikramLander(canvas, 130, 155)

  canvas.fill(130, 45, 30)
  canvas.ellipse(220, 145, 280, 100)
  canvas.ellipse(550, 145, 320, 110)

  // Mars Surface
  canvas.fill(160, 55, 35)
  canvas.rect(0, 0, cwidth, 145)

  drawIndianFlag(canvas, 210, 145)

  // Craters & Work Rock (???????????? ???)
  canvas.fill(120, 40, 25)
  canvas.ellipse(80, 60, 90, 25)
  canvas.ellipse(340, 40, 130, 30)
  canvas.ellipse(620, 80, 110, 28)

  // Target Rock for Scanning & Collection (????? ???? ?????? ??? ????)
  val targetRockX = robotX + (45.0 * robotFacing)
  canvas.fill(80, 25, 15)
  canvas.ellipse(targetRockX, 130, 28, 16)
  canvas.fill(110, 35, 20)
  canvas.ellipse(targetRockX - 3, 132, 18, 10)

  // Footprints
  canvas.fill(100, 30, 20, 160)
  canvas.noStroke()
  for (i <- 0 until maxFootprints) {
    if (fpX(i) > 0) {
      canvas.ellipse(fpX(i), fpY(i), 14, 6)
    }
  }

  // Ground Texture
  canvas.stroke(180, 70, 45)
  canvas.strokeWeight(1.0)
  for (i <- 0 until 8) {
    canvas.line(0, 15 + i * 16, cwidth, 15 + i * 16)
  }

  // Floating Dust
  canvas.noStroke()
  for (i <- 0 until particleCount) {
    val glow = (math.sin(robotTime * 3 + i) * 2 + 3).toFloat
    canvas.fill(255, 140, 90, 160)
    canvas.ellipse(particleX(i), particleY(i), glow, glow)
  }
} //> def drawRobotBackground(canvas: builtins.CanvasDraw): Unit

// ============================================================
// SCANNING LASER EFFECT (???? ??? ??? ????????)
// ============================================================
def drawLaserScan(canvas: CanvasDraw) {
  if (robotState == 1) { // Laser Scan State
    val hx = robotX + robotHeadTurn
    val hy = robotY + robotBob + 198
    val targetX = robotX + (45.0 * robotFacing) + scanAngle

    // Laser Beam (????????? ??????? ????)
    canvas.stroke(0, 255, 100, 200)
    canvas.strokeWeight(3.0)
    canvas.line(hx, hy, targetX, 130)

    // Scanning Cone Glow
    canvas.fill(0, 255, 120, 40)
    canvas.noStroke()
    canvas.ellipse(targetX, 130, 35, 12)

    // Laser Dot on Rock
    canvas.fill(255, 255, 255)
    canvas.ellipse(targetX, 130, 6, 6)
  }
} //> def drawLaserScan(canvas: builtins.CanvasDraw): Unit

// ============================================================
// ROBOT PARTS
// ============================================================
def drawRobotShadow(canvas: CanvasDraw) {
  canvas.fill(30, 10, 10, 120)
  canvas.noStroke()
  canvas.ellipse(robotX, 145, 125, 22)
} //> def drawRobotShadow(canvas: builtins.CanvasDraw): Unit

def drawRobotFoot(canvas: CanvasDraw, px: Double, py: Double, angle: Double) {
  canvas.pushMatrix()
  canvas.translate(px, py)
  canvas.rotate(angle)

  canvas.fill(0, 255, 100, 35); canvas.noStroke(); canvas.ellipse(0, 0, 58, 28)
  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2.0)
  canvas.ellipse(0, 0, 60, 32)
  canvas.fill(greenMain); canvas.noStroke(); canvas.rect(-25, -5, 50, 8)
  canvas.fill(220, 225, 230); canvas.ellipse(18, 3, 25, 18)

  canvas.popMatrix()
} //> def drawRobotFoot(canvas: builtins.CanvasDraw, px: Double, py: Double, angle: Double): Unit

def drawRobotLeg(canvas: CanvasDraw, px: Double, py: Double, legAngle: Double) {
  canvas.pushMatrix()
  canvas.translate(px, py)
  canvas.rotate(legAngle)

  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2.0)
  canvas.rect(-13, -45, 26, 48)
  canvas.fill(greenMain); canvas.stroke(greenGlow); canvas.strokeWeight(1.5)
  canvas.ellipse(0, -48, 22, 22)
  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2.0)
  canvas.rect(-15, -92, 30, 45)
  canvas.fill(greenMain); canvas.noStroke(); canvas.rect(-15, -88, 7, 35)

  canvas.popMatrix()
} //> def drawRobotLeg(canvas: builtins.CanvasDraw, px: Double, py: Double, legAngle: Double): Unit

def drawRobotArm(canvas: CanvasDraw, px: Double, py: Double, armAngle: Double, flip: Boolean) {
  canvas.pushMatrix()
  canvas.translate(px, py)
  if (flip) canvas.scale(-1, 1)
  canvas.rotate(armAngle)

  canvas.fill(greenMain); canvas.stroke(greenGlow); canvas.strokeWeight(2); canvas.ellipse(0, 0, 30, 30)
  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2); canvas.rect(-12, -48, 24, 48)
  canvas.fill(greenMain); canvas.noStroke(); canvas.ellipse(0, -50, 19, 19)
  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2); canvas.rect(-11, -92, 22, 42)
  canvas.fill(greenMain); canvas.noStroke(); canvas.rect(-11, -84, 6, 25)
  
  // Hand / Collector Claw
  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2); canvas.ellipse(0, -101, 25, 22)
  canvas.fill(greenMain); canvas.noStroke(); canvas.ellipse(0, -103, 13, 10)

  // Sample Container in Hand during Collection
  if (robotState == 2 && flip) {
    canvas.fill(220, 150, 30)
    canvas.ellipse(0, -110, 10, 10)
  }

  canvas.popMatrix()
} //> def drawRobotArm(canvas: builtins.CanvasDraw, px: Double, py: Double, armAngle: Double, flip: Boolean): Unit

def drawRobotBody(canvas: CanvasDraw) {
  val bx = robotX
  val by = robotY + robotBob

  canvas.fill(0, 255, 100, 25); canvas.noStroke(); canvas.ellipse(bx, by + 65, 105, 125)
  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2.5); canvas.rect(bx - 48, by + 20, 96, 105)
  canvas.fill(greenMain); canvas.noStroke(); canvas.rect(bx - 48, by + 28, 96, 13)

  // Reactor Core
  canvas.fill(235, 240, 242); canvas.stroke(180, 190, 195); canvas.strokeWeight(1.5); canvas.ellipse(bx, by + 90, 38, 38)
  val reactorGlow = (math.sin(robotGlow * 2) * 5 + 18).toFloat
  canvas.fill(0, 255, 100, 60); canvas.noStroke(); canvas.ellipse(bx, by + 90, 38 + reactorGlow, 38 + reactorGlow)
  canvas.fill(0, 220, 80); canvas.ellipse(bx, by + 90, 18, 18)
  canvas.fill(220, 255, 230); canvas.ellipse(bx - 4, by + 94, 6, 6)

  // ISRO Branding
  canvas.fill(20, 30, 30); canvas.text("ISRO", bx - 18, by + 115)
  canvas.fill(greenMain); canvas.noStroke(); canvas.rect(bx - 38, by + 15, 76, 15)
  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2); canvas.ellipse(bx, by + 10, 35, 22)
} //> def drawRobotBody(canvas: builtins.CanvasDraw): Unit

def drawRobotHead(canvas: CanvasDraw) {
  val hx = robotX + robotHeadTurn
  val hy = robotY + robotBob + 170

  // Signal Antenna
  canvas.stroke(180, 190, 200)
  canvas.strokeWeight(2.5)
  canvas.line(hx, hy + 60, hx, hy + 88)

  val blink = math.sin(robotTime * 12.0)
  if (blink > 0 || robotState != 0) {
    canvas.fill(255, 40, 40, 100); canvas.noStroke(); canvas.ellipse(hx, hy + 88, 18, 18)
    canvas.fill(255, 50, 50); canvas.ellipse(hx, hy + 88, 9, 9)
  } else {
    canvas.fill(40, 255, 120, 100); canvas.noStroke(); canvas.ellipse(hx, hy + 88, 18, 18)
    canvas.fill(0, 255, 100); canvas.ellipse(hx, hy + 88, 9, 9)
  }

  // Head Base
  canvas.fill(0, 255, 120, 25); canvas.noStroke(); canvas.ellipse(hx, hy, 100, 75)
  canvas.fill(greenDark); canvas.stroke(greenMain); canvas.strokeWeight(2); canvas.rect(hx - 15, hy - 43, 30, 25)
  canvas.fill(whiteRobot); canvas.stroke(whiteShadow); canvas.strokeWeight(2.5); canvas.rect(hx - 45, hy - 5, 90, 65)

  // Face Screen & LED Eyes
  canvas.fill(blackScreen); canvas.stroke(20, 40, 50); canvas.strokeWeight(3); canvas.rect(hx - 35, hy + 5, 70, 42)
  canvas.fill(greenMain); canvas.stroke(greenGlow); canvas.strokeWeight(2); canvas.ellipse(hx + 45, hy + 28, 17, 30)

  val eyeMove = if (robotState == 1) scanAngle * 0.2 else math.sin(robotTime * 2) * 2.0
  canvas.fill(40, 180, 255); canvas.ellipse(hx - 17, hy + 28 + eyeMove, 7, 7); canvas.ellipse(hx + 17, hy + 28 + eyeMove, 7, 7)
  canvas.stroke(40, 180, 255); canvas.strokeWeight(2); canvas.line(hx - 15, hy + 15, hx + 15, hy + 15)

  for (i <- 0 until 5) {
    canvas.line(hx - 25 + i * 12, hy + 48, hx - 20 + i * 12, hy + 48)
  }
} //> def drawRobotHead(canvas: builtins.CanvasDraw): Unit

// ============================================================
// MAIN DRAW
// ============================================================
def drawFuturisticRobot(canvas: CanvasDraw) {
  val by = robotY + robotBob

  // Working Arms Motion Based on State
  var rightArmAngle = -robotArmWave
  var leftArmAngle = robotArmWave

  if (robotState == 1) { // Scanning
    rightArmAngle = 45.0
    leftArmAngle = -20.0
  } else if (robotState == 2) { // Collecting
    rightArmAngle = 75.0 + armBend
    leftArmAngle = 30.0
  }

  drawRobotArm(canvas, robotX - 48, by + 105, leftArmAngle, false)
  drawRobotArm(canvas, robotX + 48, by + 105, rightArmAngle, true)

  val leftLegAngle = if (robotState == 0) math.sin(robotWalk) * 15.0 else 0.0
  val rightLegAngle = if (robotState == 0) -math.sin(robotWalk) * 15.0 else 0.0

  drawRobotLeg(canvas, robotX - 23, by + 10, leftLegAngle)
  drawRobotLeg(canvas, robotX + 23, by + 10, rightLegAngle)

  drawRobotFoot(canvas, robotX - 30 + math.sin(robotWalk) * 8, 150, leftLegAngle)
  drawRobotFoot(canvas, robotX + 30 - math.sin(robotWalk) * 8, 150, rightLegAngle)

  drawRobotBody(canvas)
  drawRobotHead(canvas)
  drawLaserScan(canvas)
} //> def drawFuturisticRobot(canvas: builtins.CanvasDraw): Unit

// ============================================================
// HUD STATUS FOR WORK
// ============================================================
def drawRobotHUD(canvas: CanvasDraw) {
  canvas.fill(255, 220, 200)
  canvas.text("ISRO MARS EXPLORER MISSION", 25, cheight - 30)

  // dynamic status update based on robot work
  if (robotState == 0) {
    canvas.fill(0, 255, 120)
    canvas.text("SYSTEM : ONLINE | SEARCHING FOR SAMPLE ROCKS", 25, cheight - 55)
  } else if (robotState == 1) {
    canvas.fill(255, 200, 0)
    canvas.text("SYSTEM : SCANNING ROCK SAMPLE WITH LASER...", 25, cheight - 55)
  } else if (robotState == 2) {
    canvas.fill(0, 200, 255)
    canvas.text("SYSTEM : COLLECTING & STORING MARS SOIL...", 25, cheight - 55)
  }

  // Energy HUD
  canvas.fill(30, 40, 45); canvas.stroke(0, 255, 120); canvas.strokeWeight(2); canvas.rect(cwidth - 190, cheight - 65, 150, 18)
  canvas.fill(0, 230, 90); canvas.rect(cwidth - 186, cheight - 61, 135, 10)
  canvas.fill(255, 255, 255); canvas.text("ENERGY 95%", cwidth - 180, cheight - 72)
} //> def drawRobotHUD(canvas: builtins.CanvasDraw): Unit

def viewRobot(canvas: CanvasDraw) {
  canvas.fill(0, 0, 0); canvas.noStroke(); canvas.rect(0, 0, cwidth, cheight)

  drawRobotBackground(canvas)
  drawRobotShadow(canvas)
  drawFuturisticRobot(canvas)
  drawRobotHUD(canvas)
} //> def viewRobot(canvas: builtins.CanvasDraw): Unit

// ============================================================
// START ANIMATION
// ============================================================
animateWithCanvasDraw { canvas =>
  updateRobot()
  viewRobot(canvas)
}