Code Sketch


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

// ============================================================
// ?? ISRO MARS EXPLORER - ULTIMATE 3D GAME
// Robot + Mars + Moon + Earth + Vikram Lander
// Keyboard Movement + Scanner + Sample Collection + Welding
// Energy + Health + Score + Missions + Footprints + Particles
// ============================================================

// ============================================================
// GAME VARIABLES
// ============================================================

var robotX = 220.0
var robotY = 150.0

var robotWalk = 0.0
var robotBob = 0.0
var robotGlow = 0.0
var robotTime = 0.0
var robotHeadTurn = 0.0
var robotArmWave = 0.0

var robotFacing = 1.0

var robotState = 0
// 0 = FREE
// 1 = SCANNING
// 2 = COLLECTING
// 3 = WELDING

var stateTimer = 0.0

var scanAngle = 0.0
var armBend = 0.0

var energy = 100.0
var health = 100.0
var score = 0
var samples = 0
var repairs = 0

var flashlight = false
var paused = false

var messageTimer = 0.0
var missionText = "SEARCH FOR MARS SAMPLE"

var cameraShake = 0.0

// ============================================================
// ROCK
// ============================================================

var rockX = 500.0
var rockY = 145.0

var rockScanned = false
var rockCollected = false

// ============================================================
// VIKRAM LANDER
// ============================================================

val landerX = 130.0
val landerY = 155.0

// ============================================================
// METEOR
// ============================================================

var meteorX = cwidth + 50.0
var meteorY = cheight - 60.0
var meteorActive = false
var meteorTimer = 0.0

// ============================================================
// FOOTPRINTS
// ============================================================

val maxFootprints = 30

val fpX =
  Array.fill(maxFootprints)(-100.0)

val fpY =
  Array.fill(maxFootprints)(140.0)

var fpIndex = 0
var lastStepX = robotX

// ============================================================
// DUST PARTICLES
// ============================================================

val particleCount = 80

val particleX =
  Array.fill(particleCount)(
    randomDouble(0, cwidth)
  )

val particleY =
  Array.fill(particleCount)(
    randomDouble(145, cheight)
  )

val particleSpeed =
  Array.fill(particleCount)(
    randomDouble(0.15, 0.65)
  )

// ============================================================
// COLORS
// ============================================================

val whiteRobot =
  cm.rgb(245, 248, 250)

val whiteShadow =
  cm.rgb(170, 185, 195)

val greenMain =
  cm.rgb(0, 210, 80)

val greenDark =
  cm.rgb(0, 105, 45)

val greenGlow =
  cm.rgb(40, 255, 120)

val blackScreen =
  cm.rgb(5, 8, 18)

val marsDark =
  cm.rgb(90, 28, 22)

val marsMid =
  cm.rgb(135, 43, 28)

val marsLight =
  cm.rgb(175, 65, 38)

// ============================================================
// MESSAGE
// ============================================================

def showMessage(txt: String) {

  missionText = txt
  messageTimer = 3.0

}

// ============================================================
// KEYBOARD CONTROLS
// ============================================================

// One-time action keys
onKeyPress { k =>

  k match {

    // SPACE = SCAN
    case Kc.VK_SPACE =>

      if (!paused) {

        robotState = 1
        stateTimer = 0.0
        scanAngle = 0.0

        showMessage("LASER SCANNING STARTED")

      }

    // E = COLLECT
    case Kc.VK_E =>

      if (!paused && rockScanned && !rockCollected) {

        robotState = 2
        stateTimer = 0.0

        showMessage("COLLECTING MARS SAMPLE")

      }

    // R = WELD / REPAIR
    case Kc.VK_R =>

      if (!paused) {

        robotState = 3
        stateTimer = 0.0

        showMessage("WELDING / REPAIR SYSTEM ACTIVE")

      }

    // F = FLASHLIGHT
    case Kc.VK_F =>

      flashlight = !flashlight

      if (flashlight)
        showMessage("FLASHLIGHT ON")
      else
        showMessage("FLASHLIGHT OFF")

    // Q = STOP TASK
    case Kc.VK_Q =>

      robotState = 0
      stateTimer = 0.0

      showMessage("TASK STOPPED")

    // P = PAUSE
    case Kc.VK_P =>

      paused = !paused

      if (paused)
        showMessage("MISSION PAUSED")
      else
        showMessage("MISSION RESUMED")

    // H = HELP
    case Kc.VK_H =>

      showMessage(
        "WASD/ARROWS MOVE | SPACE SCAN | E COLLECT | R WELD"
      )

    case _ =>

  }

}

// ============================================================
// MOVEMENT
// ============================================================

def updateMovement() {

  if (paused)
    return

  // Movement allowed during FREE state
  if (robotState == 0) {

    var dx = 0.0
    var dy = 0.0

    // --------------------------------------------------------
    // LEFT / RIGHT
    // --------------------------------------------------------

    if (
      isKeyPressed(Kc.VK_A) ||
      isKeyPressed(Kc.VK_LEFT)
    ) {

      dx -= 1.0
      robotFacing = -1.0

    }

    if (
      isKeyPressed(Kc.VK_D) ||
      isKeyPressed(Kc.VK_RIGHT)
    ) {

      dx += 1.0
      robotFacing = 1.0

    }

    // --------------------------------------------------------
    // UP / DOWN
    // --------------------------------------------------------

    if (
      isKeyPressed(Kc.VK_W) ||
      isKeyPressed(Kc.VK_UP)
    ) {

      dy += 1.0

    }

    if (
      isKeyPressed(Kc.VK_S) ||
      isKeyPressed(Kc.VK_DOWN)
    ) {

      dy -= 1.0

    }

    // --------------------------------------------------------
    // SPEED
    // --------------------------------------------------------

    var speed = 2.2

    if (isKeyPressed(Kc.VK_SHIFT)) {

      speed = 4.8

      energy -= 0.05

      if (energy < 0)
        energy = 0

    }

    // --------------------------------------------------------
    // APPLY MOVEMENT
    // --------------------------------------------------------

    robotX += dx * speed
    robotY += dy * speed

    // --------------------------------------------------------
    // BOUNDARIES
    // --------------------------------------------------------

    if (robotX < 80)
      robotX = 80

    if (robotX > cwidth - 80)
      robotX = cwidth - 80

    if (robotY < 150)
      robotY = 150

    if (robotY > cheight - 160)
      robotY = cheight - 160

    // --------------------------------------------------------
    // WALK ANIMATION
    // --------------------------------------------------------

    if (dx != 0 || dy != 0) {

      robotWalk += 0.16

      robotBob =
        math.sin(robotWalk * 2.0) * 3.5

      robotArmWave =
        math.sin(robotWalk) * 18.0

      // footprints
      if (
        math.abs(robotX - lastStepX) > 24
      ) {

        fpX(fpIndex) =
          robotX - 15.0 * robotFacing

        fpY(fpIndex) =
          142.0

        fpIndex =
          (fpIndex + 1) % maxFootprints

        lastStepX = robotX

      }

    } else {

      robotBob =
        math.sin(robotTime * 2) * 1.2

      robotArmWave = 0

    }

  }

}

// ============================================================
// GAME UPDATE
// ============================================================

def updateRobot() {

  if (paused)
    return

  robotTime += 0.018
  robotGlow += 0.08
  stateTimer += 0.018

  // ==========================================================
  // MOVEMENT
  // ==========================================================

  updateMovement()

  // ==========================================================
  // STATE 1 - SCANNING
  // ==========================================================

  if (robotState == 1) {

    robotBob =
      math.sin(robotTime * 3) * 1.5

    robotHeadTurn =
      math.sin(robotTime * 3) * 8

    scanAngle =
      math.sin(robotTime * 5) * 55

    // energy consumption

    energy -= 0.035

    if (energy < 0)
      energy = 0

    // Scan complete

    if (stateTimer > 3.0) {

      rockScanned = true
      robotState = 0

      score += 100

      showMessage(
        "ROCK SCANNED! PRESS E TO COLLECT"
      )

    }

  }

  // ==========================================================
  // STATE 2 - COLLECTION
  // ==========================================================

  if (robotState == 2) {

    robotBob = -6

    armBend =
      math.sin(robotTime * 5) * 18

    robotHeadTurn =
      -12 * robotFacing

    energy -= 0.045

    if (energy < 0)
      energy = 0

    if (stateTimer > 3.0) {

      rockCollected = true

      samples += 1
      score += 250

      robotState = 0

      showMessage(
        "MARS SAMPLE COLLECTED!"
      )

    }

  }

  // ==========================================================
  // STATE 3 - WELDING
  // ==========================================================

  if (robotState == 3) {

    robotBob =
      math.sin(robotTime * 7) * 1.5

    armBend =
      math.sin(robotTime * 9) * 20

    energy -= 0.04

    if (energy < 0)
      energy = 0

    if (stateTimer > 3.5) {

      repairs += 1

      score += 150

      health += 25

      if (health > 100)
        health = 100

      robotState = 0

      showMessage(
        "WELDING COMPLETE! ROBOT REPAIRED"
      )

    }

  }

  // ==========================================================
  // ENERGY RECOVERY
  // ==========================================================

  if (robotState == 0) {

    energy += 0.015

    if (energy > 100)
      energy = 100

  }

  // ==========================================================
  // MESSAGE TIMER
  // ==========================================================

  if (messageTimer > 0)
    messageTimer -= 0.018

  // ==========================================================
  // METEOR
  // ==========================================================

  meteorTimer += 0.018

  if (
    !meteorActive &&
    meteorTimer > 4.0
  ) {

    meteorActive = true

    meteorX =
      randomDouble(
        cwidth * 0.5,
        cwidth + 100
      )

    meteorY =
      randomDouble(
        cheight - 80,
        cheight
      )

    meteorTimer = 0

  }

  if (meteorActive) {

    meteorX -= 10
    meteorY -= 5

    if (
      meteorX < -80 ||
      meteorY < 180
    ) {

      meteorActive = false

    }

  }

  // ==========================================================
  // PARTICLES
  // ==========================================================

  for (i <- 0 until particleCount) {

    particleY(i) += particleSpeed(i)

    if (particleY(i) > cheight) {

      particleY(i) = 145

      particleX(i) =
        randomDouble(0, cwidth)

    }

  }

}

// ============================================================
// VIKRAM LANDER
// ============================================================

def drawVikramLander(
  canvas: CanvasDraw,
  lx: Double,
  ly: Double
) {

  // shadow

  canvas.fill(30, 10, 10, 100)
  canvas.noStroke()
  canvas.ellipse(
    lx,
    ly - 23,
    100,
    15
  )

  // legs

  canvas.stroke(190, 190, 200)
  canvas.strokeWeight(3)

  canvas.line(
    lx - 25,
    ly,
    lx - 45,
    ly - 25
  )

  canvas.line(
    lx + 25,
    ly,
    lx + 45,
    ly - 25
  )

  canvas.line(
    lx - 10,
    ly,
    lx - 20,
    ly - 25
  )

  canvas.line(
    lx + 10,
    ly,
    lx + 20,
    ly - 25
  )

  // pads

  canvas.fill(100, 105, 110)
  canvas.noStroke()

  canvas.ellipse(
    lx - 45,
    ly - 25,
    16,
    6
  )

  canvas.ellipse(
    lx + 45,
    ly - 25,
    16,
    6
  )

  // main body

  canvas.fill(220, 165, 30)
  canvas.stroke(180, 125, 15)
  canvas.strokeWeight(2)

  canvas.rect(
    lx - 30,
    ly,
    60,
    35
  )

  // 3D side

  canvas.fill(170, 115, 15)

  canvas.rect(
    lx + 20,
    ly,
    10,
    35
  )

  // top

  canvas.fill(195, 140, 20)

  canvas.rect(
    lx - 20,
    ly + 35,
    40,
    12
  )

  // solar panels

  canvas.fill(15, 55, 140)
  canvas.stroke(60, 120, 210)
  canvas.strokeWeight(1)

  canvas.rect(
    lx - 52,
    ly + 5,
    18,
    28
  )

  canvas.rect(
    lx + 34,
    ly + 5,
    18,
    28
  )

  // panel lines

  canvas.stroke(100, 170, 230)

  canvas.line(
    lx - 43,
    ly + 5,
    lx - 43,
    ly + 33
  )

  canvas.line(
    lx + 43,
    ly + 5,
    lx + 43,
    ly + 33
  )

  // antenna

  canvas.stroke(210, 210, 220)
  canvas.strokeWeight(2)

  canvas.line(
    lx,
    ly + 47,
    lx,
    ly + 60
  )

  canvas.fill(220, 220, 225)
  canvas.noStroke()

  canvas.ellipse(
    lx,
    ly + 62,
    18,
    10
  )

  // Indian badge

  canvas.fill(255, 153, 51)
  canvas.rect(
    lx - 11,
    ly + 22,
    22,
    4
  )

  canvas.fill(255, 255, 255)
  canvas.rect(
    lx - 11,
    ly + 18,
    22,
    4
  )

  canvas.fill(19, 136, 8)
  canvas.rect(
    lx - 11,
    ly + 14,
    22,
    4
  )

}

// ============================================================
// INDIAN FLAG
// ============================================================

def drawIndianFlag(
  canvas: CanvasDraw,
  fx: Double,
  fy: Double
) {

  canvas.stroke(220, 220, 220)
  canvas.strokeWeight(3)

  canvas.line(
    fx,
    fy,
    fx,
    fy + 85
  )

  canvas.fill(255, 210, 0)
  canvas.noStroke()

  canvas.ellipse(
    fx,
    fy + 88,
    7,
    7
  )

  val wave =
    math.sin(robotTime * 4) * 3

  // green

  canvas.fill(19, 136, 8)

  canvas.rect(
    fx,
    fy + 36 + wave,
    48,
    11
  )

  // white

  canvas.fill(255, 255, 255)

  canvas.rect(
    fx,
    fy + 47 + wave,
    48,
    11
  )

  // saffron

  canvas.fill(255, 153, 51)

  canvas.rect(
    fx,
    fy + 58 + wave,
    48,
    11
  )

  // chakra

  canvas.stroke(0, 0, 128)
  canvas.strokeWeight(1.5)

  canvas.ellipse(
    fx + 24,
    fy + 52 + wave,
    8,
    8
  )

}

// ============================================================
// MARS BACKGROUND
// ============================================================

def drawBackground(
  canvas: CanvasDraw
) {

  // ==========================================================
  // SPACE
  // ==========================================================

  canvas.fill(8, 4, 12)
  canvas.noStroke()

  canvas.rect(
    0,
    0,
    cwidth,
    cheight
  )

  // purple/red atmosphere

  canvas.fill(65, 18, 25)

  canvas.rect(
    0,
    150,
    cwidth,
    cheight - 150
  )

  // ==========================================================
  // STARS
  // ==========================================================

  canvas.fill(255, 245, 225)

  for (i <- 0 until 55) {

    val sx =
      (i * 53) % cwidth

    val sy =
      280 +
      ((i * 47) % (cheight - 280))

    val starSize =
      if (i % 5 == 0) 3 else 2

    canvas.ellipse(
      sx,
      sy,
      starSize,
      starSize
    )

  }

  // ==========================================================
  // MOON
  // ==========================================================

  val mx =
    cwidth - 135

  val my =
    cheight - 105

  canvas.fill(255, 235, 210, 35)
  canvas.ellipse(
    mx,
    my,
    125,
    125
  )

  canvas.fill(255, 240, 220, 65)
  canvas.ellipse(
    mx,
    my,
    100,
    100
  )

  canvas.fill(235, 230, 215)

  canvas.ellipse(
    mx,
    my,
    72,
    72
  )

  // moon craters

  canvas.fill(195, 190, 180)

  canvas.ellipse(
    mx - 15,
    my + 12,
    16,
    12
  )

  canvas.ellipse(
    mx + 15,
    my - 15,
    20,
    15
  )

  canvas.ellipse(
    mx + 5,
    my + 20,
    11,
    10
  )

  // ==========================================================
  // EARTH
  // ==========================================================

  canvas.fill(40, 100, 210, 180)

  canvas.ellipse(
    90,
    cheight - 80,
    30,
    30
  )

  canvas.fill(60, 170, 100, 170)

  canvas.ellipse(
    95,
    cheight - 75,
    12,
    7
  )

  // ==========================================================
  // DISTANT MOUNTAINS
  // ==========================================================

  canvas.fill(80, 25, 25)

  canvas.ellipse(
    100,
    145,
    350,
    130
  )

  canvas.ellipse(
    390,
    145,
    430,
    150
  )

  canvas.ellipse(
    720,
    145,
    380,
    140
  )

  // ==========================================================
  // LANDER
  // ==========================================================

  drawVikramLander(
    canvas,
    landerX,
    landerY
  )

  // ==========================================================
  // MID MOUNTAINS
  // ==========================================================

  canvas.fill(125, 42, 29)

  canvas.ellipse(
    250,
    145,
    300,
    110
  )

  canvas.ellipse(
    570,
    145,
    350,
    120
  )

  // ==========================================================
  // MARS GROUND
  // ==========================================================

  canvas.fill(marsLight)

  canvas.rect(
    0,
    0,
    cwidth,
    145
  )

  // ==========================================================
  // FLAG
  // ==========================================================

  drawIndianFlag(
    canvas,
    205,
    145
  )

  // ==========================================================
  // CRATERS
  // ==========================================================

  canvas.fill(110, 35, 23)

  canvas.ellipse(
    70,
    60,
    100,
    28
  )

  canvas.ellipse(
    350,
    45,
    135,
    32
  )

  canvas.ellipse(
    650,
    75,
    120,
    30
  )

  // ==========================================================
  // TARGET ROCK
  // ==========================================================

  if (!rockCollected) {

    canvas.fill(60, 20, 13)

    canvas.ellipse(
      rockX,
      rockY,
      40,
      24
    )

    canvas.fill(100, 32, 18)

    canvas.ellipse(
      rockX - 5,
      rockY + 3,
      24,
      12
    )

    // scanned glow

    if (rockScanned) {

      canvas.fill(
        0,
        255,
        100,
        70
      )

      canvas.ellipse(
        rockX,
        rockY,
        55,
        35
      )

    }

  }

  // ==========================================================
  // FOOTPRINTS
  // ==========================================================

  canvas.fill(
    90,
    28,
    20,
    170
  )

  canvas.noStroke()

  for (i <- 0 until maxFootprints) {

    if (fpX(i) > 0) {

      canvas.ellipse(
        fpX(i),
        fpY(i),
        15,
        7
      )

    }

  }

  // ==========================================================
  // GROUND LINES
  // ==========================================================

  canvas.stroke(
    195,
    78,
    48
  )

  canvas.strokeWeight(1)

  for (i <- 0 until 8) {

    canvas.line(
      0,
      15 + i * 16,
      cwidth,
      15 + i * 16
    )

  }

  // ==========================================================
  // DUST
  // ==========================================================

  canvas.noStroke()

  for (i <- 0 until particleCount) {

    val glow =
      (
        math.sin(
          robotTime * 3 + i
        ) * 2 + 3
      ).toFloat

    canvas.fill(
      255,
      150,
      100,
      130
    )

    canvas.ellipse(
      particleX(i),
      particleY(i),
      glow,
      glow
    )

  }

  // ==========================================================
  // METEOR
  // ==========================================================

  if (meteorActive) {

    canvas.stroke(
      255,
      235,
      170,
      220
    )

    canvas.strokeWeight(3)

    canvas.line(
      meteorX,
      meteorY,
      meteorX + 45,
      meteorY + 27
    )

    canvas.fill(
      255,
      255,
      255
    )

    canvas.noStroke()

    canvas.ellipse(
      meteorX,
      meteorY,
      7,
      7
    )

  }

}

// ============================================================
// FLASHLIGHT
// ============================================================

def drawFlashlight(
  canvas: CanvasDraw
) {

  if (flashlight) {

    val hx =
      robotX + robotFacing * 55

    val hy =
      robotY + robotBob + 185

    canvas.fill(
      150,
      255,
      180,
      25
    )

    canvas.noStroke()

    canvas.ellipse(
      hx + robotFacing * 65,
      hy - 30,
      150,
      80
    )

    canvas.stroke(
      150,
      255,
      180,
      120
    )

    canvas.strokeWeight(2)

    canvas.line(
      hx,
      hy,
      hx + robotFacing * 130,
      hy - 35
    )

  }

}

// ============================================================
// LASER SCANNER
// ============================================================

def drawScanner(
  canvas: CanvasDraw
) {

  if (robotState == 1) {

    val hx =
      robotX + robotHeadTurn

    val hy =
      robotY +
      robotBob +
      198

    val targetX =
      rockX + scanAngle * 0.35

    // outer beam

    canvas.stroke(
      0,
      255,
      100,
      50
    )

    canvas.strokeWeight(8)

    canvas.line(
      hx,
      hy,
      targetX,
      rockY
    )

    // laser

    canvas.stroke(
      0,
      255,
      120,
      230
    )

    canvas.strokeWeight(2.5)

    canvas.line(
      hx,
      hy,
      targetX,
      rockY
    )

    // scanner circle

    canvas.fill(
      0,
      255,
      120,
      70
    )

    canvas.noStroke()

    canvas.ellipse(
      targetX,
      rockY,
      35,
      35
    )

    canvas.fill(
      255,
      255,
      255
    )

    canvas.ellipse(
      targetX,
      rockY,
      7,
      7
    )

  }

}

// ============================================================
// WELDING EFFECT
// ============================================================

def drawWelding(
  canvas: CanvasDraw
) {

  if (robotState == 3) {

    val wx =
      robotX +
      robotFacing * 75

    val wy =
      robotY +
      robotBob +
      105

    canvas.fill(
      255,
      210,
      40,
      80
    )

    canvas.noStroke()

    canvas.ellipse(
      wx,
      wy,
      35,
      35
    )

    canvas.fill(
      255,
      255,
      255
    )

    canvas.ellipse(
      wx,
      wy,
      12,
      12
    )

    // sparks

    canvas.stroke(
      255,
      180,
      40
    )

    canvas.strokeWeight(2)

    for (i <- 0 until 8) {

      val sx =
        wx +
        math.sin(
          robotTime * 10 + i
        ) * 22

      val sy =
        wy +
        math.cos(
          robotTime * 11 + i
        ) * 22

      canvas.line(
        wx,
        wy,
        sx,
        sy
      )

    }

  }

}

// ============================================================
// ROBOT SHADOW
// ============================================================

def drawRobotShadow(
  canvas: CanvasDraw
) {

  canvas.fill(
    30,
    10,
    10,
    110
  )

  canvas.noStroke()

  canvas.ellipse(
    robotX,
    145,
    135,
    22
  )

}

// ============================================================
// ROBOT FOOT
// ============================================================

def drawRobotFoot(
  canvas: CanvasDraw,
  px: Double,
  py: Double,
  angle: Double
) {

  canvas.pushMatrix()

  canvas.translate(
    px,
    py
  )

  canvas.rotate(angle)

  // glow

  canvas.fill(
    0,
    255,
    100,
    35
  )

  canvas.noStroke()

  canvas.ellipse(
    0,
    0,
    64,
    32
  )

  // foot

  canvas.fill(
    whiteRobot
  )

  canvas.stroke(
    whiteShadow
  )

  canvas.strokeWeight(2)

  canvas.ellipse(
    0,
    0,
    60,
    32
  )

  // green strip

  canvas.fill(
    greenMain
  )

  canvas.noStroke()

  canvas.rect(
    -25,
    -5,
    50,
    8
  )

  // metallic side

  canvas.fill(
    215,
    220,
    225
  )

  canvas.ellipse(
    18,
    3,
    24,
    17
  )

  canvas.popMatrix()

}

// ============================================================
// ROBOT LEG
// ============================================================

def drawRobotLeg(
  canvas: CanvasDraw,
  px: Double,
  py: Double,
  angle: Double
) {

  canvas.pushMatrix()

  canvas.translate(
    px,
    py
  )

  canvas.rotate(angle)

  // upper leg

  canvas.fill(
    whiteRobot
  )

  canvas.stroke(
    whiteShadow
  )

  canvas.strokeWeight(2)

  canvas.rect(
    -13,
    -45,
    26,
    48
  )

  // joint

  canvas.fill(
    greenMain
  )

  canvas.stroke(
    greenGlow
  )

  canvas.strokeWeight(2)

  canvas.ellipse(
    0,
    -48,
    22,
    22
  )

  // lower leg

  canvas.fill(
    whiteRobot
  )

  canvas.stroke(
    whiteShadow
  )

  canvas.strokeWeight(2)

  canvas.rect(
    -15,
    -92,
    30,
    45
  )

  // green armor

  canvas.fill(
    greenMain
  )

  canvas.noStroke()

  canvas.rect(
    -15,
    -88,
    7,
    35
  )

  canvas.popMatrix()

}

// ============================================================
// ROBOT ARM
// ============================================================

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
  )

  // shoulder

  canvas.fill(
    greenMain
  )

  canvas.stroke(
    greenGlow
  )

  canvas.strokeWeight(2)

  canvas.ellipse(
    0,
    0,
    30,
    30
  )

  // upper arm

  canvas.fill(
    whiteRobot
  )

  canvas.stroke(
    whiteShadow
  )

  canvas.strokeWeight(2)

  canvas.rect(
    -12,
    -48,
    24,
    48
  )

  // elbow

  canvas.fill(
    greenMain
  )

  canvas.noStroke()

  canvas.ellipse(
    0,
    -50,
    19,
    19
  )

  // forearm

  canvas.fill(
    whiteRobot
  )

  canvas.stroke(
    whiteShadow
  )

  canvas.strokeWeight(2)

  canvas.rect(
    -11,
    -92,
    22,
    42
  )

  // green panel

  canvas.fill(
    greenMain
  )

  canvas.noStroke()

  canvas.rect(
    -11,
    -84,
    6,
    25
  )

  // hand

  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

  if (
    robotState == 2 &&
    flip
  ) {

    canvas.fill(
      220,
      150,
      30
    )

    canvas.ellipse(
      0,
      -113,
      13,
      13
    )

  }

  canvas.popMatrix()

}

// ============================================================
// ROBOT BODY
// ============================================================

def drawRobotBody(
  canvas: CanvasDraw
) {

  val bx =
    robotX

  val by =
    robotY + robotBob

  // energy aura

  canvas.fill(
    0,
    255,
    100,
    25
  )

  canvas.noStroke()

  canvas.ellipse(
    bx,
    by + 65,
    120,
    140
  )

  // body shadow side

  canvas.fill(
    190,
    200,
    205
  )

  canvas.rect(
    bx - 48,
    by + 20,
    96,
    105
  )

  // main body

  canvas.fill(
    whiteRobot
  )

  canvas.stroke(
    whiteShadow
  )

  canvas.strokeWeight(2.5)

  canvas.rect(
    bx - 48,
    by + 20,
    88,
    105
  )

  // green armor

  canvas.fill(
    greenMain
  )

  canvas.noStroke()

  canvas.rect(
    bx - 48,
    by + 28,
    96,
    13
  )

  // reactor outer

  canvas.fill(
    235,
    240,
    242
  )

  canvas.stroke(
    180,
    190,
    195
  )

  canvas.strokeWeight(1.5)

  canvas.ellipse(
    bx,
    by + 90,
    40,
    40
  )

  // reactor glow

  val rg =
    (
      math.sin(robotGlow * 2) * 5 + 18
    ).toFloat

  canvas.fill(
    0,
    255,
    100,
    60
  )

  canvas.noStroke()

  canvas.ellipse(
    bx,
    by + 90,
    40 + rg,
    40 + rg
  )

  canvas.fill(
    0,
    220,
    80
  )

  canvas.ellipse(
    bx,
    by + 90,
    19,
    19
  )

  canvas.fill(
    220,
    255,
    230
  )

  canvas.ellipse(
    bx - 4,
    by + 94,
    6,
    6
  )

  // ISRO

  canvas.fill(
    20,
    30,
    30
  )

  canvas.text(
    "ISRO",
    bx - 18,
    by + 115
  )

  // shoulder plate

  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
  )

}

// ============================================================
// ROBOT HEAD
// ============================================================

def drawRobotHead(
  canvas: CanvasDraw
) {

  val hx =
    robotX + robotHeadTurn

  val hy =
    robotY +
    robotBob +
    170

  // antenna

  canvas.stroke(
    180,
    190,
    200
  )

  canvas.strokeWeight(2.5)

  canvas.line(
    hx,
    hy + 60,
    hx,
    hy + 88
  )

  // beacon

  val blink =
    math.sin(
      robotTime * 12
    )

  if (
    blink > 0 ||
    robotState != 0
  ) {

    canvas.fill(
      255,
      40,
      40,
      100
    )

    canvas.noStroke()

    canvas.ellipse(
      hx,
      hy + 88,
      20,
      20
    )

    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,
      20,
      20
    )

    canvas.fill(
      0,
      255,
      100
    )

    canvas.ellipse(
      hx,
      hy + 88,
      9,
      9
    )

  }

  // head glow

  canvas.fill(
    0,
    255,
    120,
    25
  )

  canvas.noStroke()

  canvas.ellipse(
    hx,
    hy,
    105,
    80
  )

  // neck

  canvas.fill(
    greenDark
  )

  canvas.stroke(
    greenMain
  )

  canvas.strokeWeight(2)

  canvas.rect(
    hx - 15,
    hy - 43,
    30,
    25
  )

  // head

  canvas.fill(
    whiteRobot
  )

  canvas.stroke(
    whiteShadow
  )

  canvas.strokeWeight(2.5)

  canvas.rect(
    hx - 45,
    hy - 5,
    90,
    65
  )

  // screen

  canvas.fill(
    blackScreen
  )

  canvas.stroke(
    20,
    40,
    50
  )

  canvas.strokeWeight(3)

  canvas.rect(
    hx - 35,
    hy + 5,
    70,
    42
  )

  // side sensor

  canvas.fill(
    greenMain
  )

  canvas.stroke(
    greenGlow
  )

  canvas.strokeWeight(2)

  canvas.ellipse(
    hx + 45,
    hy + 28,
    17,
    30
  )

  // eyes

  val eyeMove =
    if (robotState == 1)
      scanAngle * 0.12
    else
      math.sin(
        robotTime * 2
      ) * 2

  canvas.fill(
    40,
    180,
    255
  )

  canvas.ellipse(
    hx - 17,
    hy + 28 + eyeMove,
    7,
    7
  )

  canvas.ellipse(
    hx + 17,
    hy + 28 + eyeMove,
    7,
    7
  )

  // face line

  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
    )

  }

}

// ============================================================
// ROBOT
// ============================================================

def drawRobot(
  canvas: CanvasDraw
) {

  val by =
    robotY + robotBob

  var leftArm =
    robotArmWave

  var rightArm =
    -robotArmWave

  if (robotState == 1) {

    leftArm = -20
    rightArm = 45

  }

  if (robotState == 2) {

    leftArm = 30
    rightArm =
      75 + armBend

  }

  if (robotState == 3) {

    leftArm = -10
    rightArm =
      65 + armBend

  }

  // arms

  drawRobotArm(
    canvas,
    robotX - 48,
    by + 105,
    leftArm,
    false
  )

  drawRobotArm(
    canvas,
    robotX + 48,
    by + 105,
    rightArm,
    true
  )

  // legs

  val leftLeg =
    if (robotState == 0)
      math.sin(robotWalk) * 15
    else
      0.0

  val rightLeg =
    if (robotState == 0)
      -math.sin(robotWalk) * 15
    else
      0.0

  drawRobotLeg(
    canvas,
    robotX - 23,
    by + 10,
    leftLeg
  )

  drawRobotLeg(
    canvas,
    robotX + 23,
    by + 10,
    rightLeg
  )

  // feet

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

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

  // body

  drawRobotBody(canvas)

  // head

  drawRobotHead(canvas)

}

// ============================================================
// HUD
// ============================================================

def drawHUD(
  canvas: CanvasDraw
) {

  // top mission panel

  canvas.fill(
    5,
    12,
    20,
    210
  )

  canvas.stroke(
    0,
    255,
    120,
    160
  )

  canvas.strokeWeight(2)

  canvas.rect(
    15,
    cheight - 115,
    360,
    90
  )

  canvas.fill(
    255,
    255,
    255
  )

  canvas.text(
    "?? ISRO MARS EXPLORER",
    30,
    cheight - 48
  )

  canvas.fill(
    greenGlow
  )

  canvas.text(
    "MISSION: " + missionText,
    30,
    cheight - 72
  )

  canvas.fill(
    255,
    220,
    100
  )

  canvas.text(
    "SCORE: " + score +
    "   SAMPLES: " + samples,
    30,
    cheight - 95
  )

  // ==========================================================
  // ENERGY
  // ==========================================================

  canvas.fill(
    30,
    40,
    45
  )

  canvas.stroke(
    0,
    255,
    120
  )

  canvas.strokeWeight(2)

  canvas.rect(
    cwidth - 220,
    cheight - 55,
    180,
    18
  )

  canvas.fill(
    0,
    230,
    90
  )

  canvas.rect(
    cwidth - 216,
    cheight - 51,
    1.72 * energy,
    10
  )

  canvas.fill(
    255,
    255,
    255
  )

  canvas.text(
    "ENERGY " +
    energy.toInt +
    "%",
    cwidth - 210,
    cheight - 67
  )

  // ==========================================================
  // HEALTH
  // ==========================================================

  canvas.fill(
    30,
    40,
    45
  )

  canvas.stroke(
    255,
    70,
    70
  )

  canvas.strokeWeight(2)

  canvas.rect(
    cwidth - 220,
    cheight - 90,
    180,
    18
  )

  canvas.fill(
    230,
    50,
    50
  )

  canvas.rect(
    cwidth - 216,
    cheight - 86,
    1.72 * health,
    10
  )

  canvas.fill(
    255,
    255,
    255
  )

  canvas.text(
    "HEALTH " +
    health.toInt +
    "%",
    cwidth - 210,
    cheight - 102
  )

  // ==========================================================
  // CONTROLS
  // ==========================================================

  canvas.fill(
    5,
    10,
    18,
    190
  )

  canvas.stroke(
    80,
    180,
    255,
    130
  )

  canvas.strokeWeight(1.5)

  canvas.rect(
    15,
    15,
    270,
    85
  )

  canvas.fill(
    200,
    230,
    255
  )

  canvas.text(
    "WASD / ARROWS : MOVE",
    28,
    32
  )

  canvas.text(
    "SHIFT : TURBO",
    28,
    50
  )

  canvas.text(
    "SPACE : SCAN",
    28,
    68
  )

  canvas.text(
    "E : COLLECT    R : WELD",
    28,
    86
  )

  canvas.text(
    "F : LIGHT    P : PAUSE",
    300,
    32
  )

  // ==========================================================
  // PAUSE
  // ==========================================================

  if (paused) {

    canvas.fill(
      0,
      0,
      0,
      180
    )

    canvas.noStroke()

    canvas.rect(
      0,
      0,
      cwidth,
      cheight
    )

    canvas.fill(
      255,
      255,
      255
    )

    canvas.text(
      "MISSION PAUSED",
      cwidth / 2 - 70,
      cheight / 2
    )

    canvas.text(
      "PRESS P TO RESUME",
      cwidth / 2 - 80,
      cheight / 2 - 30
    )

  }

}

// ============================================================
// MAIN DRAW
// ============================================================

def viewGame(
  canvas: CanvasDraw
) {

  canvas.fill(
    0,
    0,
    0
  )

  canvas.noStroke()

  canvas.rect(
    0,
    0,
    cwidth,
    cheight
  )

  drawBackground(canvas)

  drawRobotShadow(canvas)

  drawFlashlight(canvas)

  drawScanner(canvas)

  drawWelding(canvas)

  drawRobot(canvas)

  drawHUD(canvas)

  // ==========================================================
  // CENTER MESSAGE
  // ==========================================================

  if (messageTimer > 0) {

    canvas.fill(
      0,
      0,
      0,
      170
    )

    canvas.noStroke()

    canvas.rect(
      cwidth / 2 - 220,
      cheight - 145,
      440,
      35
    )

    canvas.fill(
      greenGlow
    )

    canvas.text(
      missionText,
      cwidth / 2 - 190,
      cheight - 122
    )

  }

}

// ============================================================
// START GAME
// ============================================================

activateCanvas()

animateWithCanvasDraw { canvas =>

  updateRobot()

  viewGame(canvas)

}