Code Sketch


SIDDHI SABALE
By: Mhalsakant School
Category: Art
cleari()
originBottomLeft()

// ============================================================
// ISRO MARS EXPLORER GAME
// COMPLETE WORKING VERSION
// ============================================================

// ============================================================
// ROBOT POSITION / ANIMATION
// ============================================================

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

// ============================================================
// GAME STATE
// 0 = WALK
// 1 = SCAN
// 2 = COLLECT
// 3 = WELD
// ============================================================

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

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

var energy = 100 //> var energy: Int = 100
var samples = 0 //> var samples: Int = 0
var score = 0 //> var score: Int = 0

var missionComplete = false //> var missionComplete: Boolean = false
var gamePaused = false //> var gamePaused: Boolean = false

var weldProgress = 0.0 //> var weldProgress: Double = 0.0

// ============================================================
// KEYBOARD SPEED
// ============================================================

val moveSpeed = 2.8 //> val moveSpeed: Double = 2.8

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

var meteorX = cwidth + 50.0 //> var meteorX: Double = 825.0
var meteorY = cheight - 50.0 //> var meteorY: Double = 477.0
var meteorActive = false //> var meteorActive: Boolean = false
var meteorTimer = 0.0 //> var meteorTimer: Double = 0.0

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

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

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

val particleCount = 70 //> val particleCount: Int = 70

val particleX =
  Array.fill(particleCount)(
    randomDouble(0, cwidth)
  ) //> val particleX: Array[Double] = Array(633.0222040349222, 563.9013373845515, 132.56695372892983, 9.593878398617425, 73.33847570466203, 640.0859123675743, 56.77278082067481, 145.06253277405074, 704.5754059142043, 407.87416443543634, 138.0105136450392, 692.6899878231916, 464.58019251765063, 599.5101848063889, 62.470728037223104, 103.07445582192115, 364.96465882877, 573.4385607981977, 644.1169072635299, 20.382347027016046, 371.4385490727127, 135.64123211985313, 132.5517042956706, 253.73490182906795, 369.312651022744, 137.89850346838787, 749.1432218132743, 736.5385232193136, 749.2717591624647, 145.9547362223835, 693.6285225316487, 532.7425849370943, 79.74419510579159, 486.7550446448635, 375.49672488190544, 630.4291542207974, 168.88785145882233, 224.32539216130687, 729.5073732076905, 256.27890...

val particleY =
  Array.fill(particleCount)(
    randomDouble(145, cheight)
  ) //> val particleY: Array[Double] = Array(319.37214709701504, 236.76898889522164, 407.2518070359643, 159.2601606614719, 344.1626398855843, 290.4468086196124, 391.91659904188, 397.9843940064643, 513.0679525134594, 422.3153607487331, 268.1195175064586, 308.08190032404184, 214.89127724539847, 347.17682153509253, 159.27900400524325, 378.02649762197086, 238.4912823383441, 210.49193231613984, 397.95597224413814, 289.6710477648246, 450.33629290889377, 314.8377490175859, 317.36550354019687, 347.43123427045884, 482.8384966144226, 283.301012287448, 410.40048763003034, 326.92669313374233, 422.5253830640566, 462.8930923125327, 430.02802572386696, 322.0549311482906, 331.82217156376913, 179.33597524481124, 461.6049514879606, 222.45621193504365, 154.3733784503408, 475.770072196142, 394.7818083696882, 270.2...

val particleSpeed =
  Array.fill(particleCount)(
    randomDouble(0.15, 0.6)
  ) //> val particleSpeed: Array[Double] = Array(0.40520587267413877, 0.34349348026815496, 0.27947998349015846, 0.2610259096583358, 0.5320772198561498, 0.17333446581047302, 0.5096845676245629, 0.37060313430793035, 0.5114895059981269, 0.32115431500185787, 0.561891355726905, 0.3359884498883521, 0.458767346455196, 0.17837210224483965, 0.15577343139941038, 0.2936608065453644, 0.5748209054435249, 0.1995301158942573, 0.4362194575752104, 0.17449372043934755, 0.5285188890697406, 0.5357156861386225, 0.3768481789941092, 0.3791614076939395, 0.3986056118646021, 0.5528462984589374, 0.5442944509838047, 0.2598813168736669, 0.5517504475019707, 0.39301053432488814, 0.3061297818695923, 0.537998715528694, 0.5900078246869098, 0.21444978324043584, 0.1665008761797808, 0.5400933132257997, 0.15921722778023745, 0.28722...

// ============================================================
// 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))

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

var targetRockX = 520.0 //> var targetRockX: Double = 520.0
val targetRockY = 130.0 //> val targetRockY: Double = 130.0

// ============================================================
// KEYBOARD HELP
// ============================================================

onKeyPress { k =>

  // ----------------------------------------------------------
  // SPACE = SCAN
  // ----------------------------------------------------------

  if (k == Kc.VK_SPACE && !gamePaused) {

    if (robotState == 0) {

      robotState = 1
      stateTimer = 0.0
      scanAngle = 0.0

    }

  }

  // ----------------------------------------------------------
  // E = COLLECT
  // ----------------------------------------------------------

  if (k == Kc.VK_E && !gamePaused) {

    if (robotState == 1 || robotState == 0) {

      robotState = 2
      stateTimer = 0.0

    }

  }

  // ----------------------------------------------------------
  // F = WELD
  // ----------------------------------------------------------

  if (k == Kc.VK_F && !gamePaused) {

    robotState = 3
    stateTimer = 0.0

  }

  // ----------------------------------------------------------
  // NUMBER MODES
  // ----------------------------------------------------------

  if (k == Kc.VK_1) {

    robotState = 0
    stateTimer = 0.0

  }

  if (k == Kc.VK_2) {

    robotState = 1
    stateTimer = 0.0

  }

  if (k == Kc.VK_3) {

    robotState = 2
    stateTimer = 0.0

  }

  if (k == Kc.VK_4) {

    robotState = 3
    stateTimer = 0.0

  }

  // ----------------------------------------------------------
  // P = PAUSE
  // ----------------------------------------------------------

  if (k == Kc.VK_P) {

    gamePaused = !gamePaused

  }

  // ----------------------------------------------------------
  // R = RESET
  // ----------------------------------------------------------

  if (k == Kc.VK_R) {

    robotX = 220.0
    robotY = 145.0

    energy = 100
    samples = 0
    score = 0

    weldProgress = 0.0

    robotState = 0
    stateTimer = 0.0

    missionComplete = false
    gamePaused = false

  }
}

// ============================================================
// UPDATE ROBOT
// ============================================================

def updateRobot() {

  if (gamePaused) {
    return
  }

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

  // ----------------------------------------------------------
  // KEYBOARD MOVEMENT
  // ----------------------------------------------------------

  var moving = false

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

    robotX -= moveSpeed
    robotFacing = -1.0
    moving = true

  }

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

    robotX += moveSpeed
    robotFacing = 1.0
    moving = true

  }

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

    robotY += moveSpeed * 0.7

    if (robotY > 190) {
      robotY = 190
    }

  }

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

    robotY -= moveSpeed * 0.7

    if (robotY < 145) {
      robotY = 145
    }

  }

  // ----------------------------------------------------------
  // SCREEN BOUNDARY
  // ----------------------------------------------------------

  if (robotX < 80) {
    robotX = 80
  }

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

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

  if (moving && robotState == 0) {

    robotWalk += 0.16

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

    robotArmWave =
      math.sin(robotWalk) * 22.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

    }

  }
  else {

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

    robotArmWave =
      math.sin(robotTime) * 5.0

  }

  // ----------------------------------------------------------
  // SCAN MODE
  // ----------------------------------------------------------

  if (robotState == 1) {

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

    scanAngle =
      math.sin(robotTime * 4.0) * 30.0

    robotHeadTurn =
      -8.0 * robotFacing

    robotArmWave = 5.0

    if (stateTimer > 5.0) {

      robotState = 0
      stateTimer = 0.0

      score += 10

    }

  }

  // ----------------------------------------------------------
  // COLLECT MODE
  // ----------------------------------------------------------

  if (robotState == 2) {

    robotBob = -5.0

    armBend =
      math.sin(robotTime * 4.0) * 18.0

    robotHeadTurn =
      -12.0 * robotFacing

    if (stateTimer > 3.5) {

      samples += 1
      score += 25

      robotState = 0
      stateTimer = 0.0

      energy -= 5

      if (energy < 0) {
        energy = 0
      }

    }

  }

  // ----------------------------------------------------------
  // WELD MODE
  // ----------------------------------------------------------

  if (robotState == 3) {

    robotBob = -2.0

    armBend =
      math.sin(robotTime * 8.0) * 12.0

    robotHeadTurn =
      8.0 * robotFacing

    weldProgress += 0.8

    if (weldProgress > 100) {
      weldProgress = 100
    }

    if (stateTimer > 5.0) {

      score += 50

      energy -= 8

      if (energy < 0) {
        energy = 0
      }

      robotState = 0
      stateTimer = 0.0
      weldProgress = 0.0

    }

  }

  // ----------------------------------------------------------
  // MISSION CHECK
  // ----------------------------------------------------------

  if (samples >= 3 && score >= 75) {

    missionComplete = true

  }

  // ----------------------------------------------------------
  // METEOR
  // ----------------------------------------------------------

  meteorTimer += 0.018

  if (
    !meteorActive &&
    meteorTimer > 4.0
  ) {

    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

    }

  }

  // ----------------------------------------------------------
  // DUST
  // ----------------------------------------------------------

  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
  )

  // Landing pads

  canvas.fill(120, 120, 130)
  canvas.noStroke()

  canvas.ellipse(
    lx - 45,
    ly - 22,
    12,
    5
  )

  canvas.ellipse(
    lx + 45,
    ly - 22,
    12,
    5
  )

  // Body

  canvas.fill(220, 165, 30)
  canvas.stroke(180, 130, 20)
  canvas.strokeWeight(1.5)

  canvas.rect(
    lx - 28,
    ly,
    56,
    32
  )

  // Top

  canvas.fill(190, 140, 20)

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

  // Solar panels

  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
  )

  // Antenna

  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
  )

  // Indian badge

  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 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

  // Saffron

  canvas.fill(255, 153, 51)

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

  // White

  canvas.fill(255, 255, 255)

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

  // Chakra

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

  canvas.ellipse(
    fx + 22,
    fy + 52.5 + wave,
    7.5,
    7.5
  )

  // Green

  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
// ============================================================

def drawRobotBackground(
  canvas: CanvasDraw
) {

  // Sky

  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
    )

  }

  // Meteor

  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
  )

  // Moon craters

  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
  )

  // Mountains

  canvas.fill(90, 30, 25)

  canvas.ellipse(
    100,
    145,
    320,
    120
  )

  canvas.ellipse(
    350,
    145,
    450,
    160
  )

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

  // Lander

  drawVikramLander(
    canvas,
    130,
    155
  )

  // Mid mountains

  canvas.fill(130, 45, 30)

  canvas.ellipse(
    220,
    145,
    280,
    100
  )

  canvas.ellipse(
    550,
    145,
    320,
    110
  )

  // Mars ground

  canvas.fill(160, 55, 35)

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

  // Flag

  drawIndianFlag(
    canvas,
    210,
    145
  )

  // Craters

  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

  canvas.fill(80, 25, 15)

  canvas.ellipse(
    targetRockX,
    targetRockY,
    32,
    18
  )

  canvas.fill(110, 35, 20)

  canvas.ellipse(
    targetRockX - 3,
    targetRockY + 2,
    18,
    10
  )

  // Target marker

  if (robotState == 1) {

    canvas.stroke(
      0,
      255,
      120
    )

    canvas.strokeWeight(2)

    canvas.noFill()

    canvas.ellipse(
      targetRockX,
      targetRockY,
      45,
      28
    )

  }

  // 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 lines

  canvas.stroke(
    180,
    70,
    45
  )

  canvas.strokeWeight(1.0)

  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,
      140,
      90,
      160
    )

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

  }

} //> def drawRobotBackground(canvas: builtins.CanvasDraw): Unit

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

def drawRobotShadow(
  canvas: CanvasDraw
) {

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

  canvas.noStroke()

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

} //> def drawRobotShadow(canvas: builtins.CanvasDraw): Unit

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

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

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

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

// ============================================================
// 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
  )

  // Joint

  canvas.fill(greenMain)
  canvas.noStroke()

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

  // Lower arm

  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

  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

  if (
    robotState == 2 &&
    flip
  ) {

    canvas.fill(
      220,
      150,
      30
    )

    canvas.ellipse(
      0,
      -110,
      10,
      10
    )

  }

  // Welding sparks

  if (
    robotState == 3 &&
    flip
  ) {

    canvas.fill(
      255,
      220,
      40
    )

    canvas.ellipse(
      8,
      -108,
      5,
      5
    )

    canvas.fill(
      255,
      120,
      20
    )

    canvas.ellipse(
      -7,
      -112,
      4,
      4
    )

    canvas.ellipse(
      6,
      -118,
      3,
      3
    )

  }

  canvas.popMatrix()

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

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

def drawRobotBody(
  canvas: CanvasDraw
) {

  val bx = robotX

  val by =
    robotY + robotBob

  // Glow

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

  canvas.noStroke()

  canvas.ellipse(
    bx,
    by + 65,
    105,
    125
  )

  // Body

  canvas.fill(whiteRobot)

  canvas.stroke(whiteShadow)
  canvas.strokeWeight(2.5)

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

  // Green stripe

  canvas.fill(greenMain)
  canvas.noStroke()

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

  // Reactor

  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
  )

  // ISRO

  canvas.fill(
    20,
    30,
    30
  )

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

  // Chest stripe

  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

// ============================================================
// 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

  if (
    math.sin(robotTime * 12.0) > 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 glow

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

  canvas.noStroke()

  canvas.ellipse(
    hx,
    hy,
    100,
    75
  )

  // 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 LED

  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.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
  )

  // Face line

  canvas.stroke(
    40,
    180,
    255
  )

  canvas.strokeWeight(2)

  canvas.line(
    hx - 15,
    hy + 15,
    hx + 15,
    hy + 15
  )

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

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

def drawLaserScan(
  canvas: CanvasDraw
) {

  if (robotState == 1) {

    val hx =
      robotX + robotHeadTurn

    val hy =
      robotY +
      robotBob +
      198

    val targetX =
      targetRockX + scanAngle

    // Beam

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

    canvas.strokeWeight(3.0)

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

    // Glow

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

    canvas.noStroke()

    canvas.ellipse(
      targetX,
      targetRockY,
      35,
      12
    )

    // Dot

    canvas.fill(
      255,
      255,
      255
    )

    canvas.ellipse(
      targetX,
      targetRockY,
      6,
      6
    )

  }

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

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

def drawWelding(
  canvas: CanvasDraw
) {

  if (robotState == 3) {

    val wx =
      robotX +
      (55.0 * robotFacing)

    val wy =
      robotY + 135

    canvas.stroke(
      255,
      180,
      30,
      180
    )

    canvas.strokeWeight(2)

    canvas.line(
      wx,
      wy,
      wx + 25,
      wy - 10
    )

    canvas.line(
      wx,
      wy,
      wx - 20,
      wy - 15
    )

    canvas.fill(
      255,
      230,
      60
    )

    canvas.noStroke()

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

  }

} //> def drawWelding(canvas: builtins.CanvasDraw): Unit

// ============================================================
// ROBOT DRAW
// ============================================================

def drawFuturisticRobot(
  canvas: CanvasDraw
) {

  val by =
    robotY + robotBob

  var rightArmAngle =
    -robotArmWave

  var leftArmAngle =
    robotArmWave

  if (robotState == 1) {

    rightArmAngle = 45.0
    leftArmAngle = -20.0

  }

  if (robotState == 2) {

    rightArmAngle =
      75.0 + armBend

    leftArmAngle = 30.0

  }

  if (robotState == 3) {

    rightArmAngle =
      60.0 + armBend

    leftArmAngle = -10.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)

  drawWelding(canvas)

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

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

def drawRobotHUD(
  canvas: CanvasDraw
) {

  // Title

  canvas.fill(
    255,
    220,
    200
  )

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

  // Status

  canvas.fill(
    0,
    255,
    120
  )

  if (robotState == 0) {

    canvas.text(
      "WALKING / EXPLORING",
      25,
      cheight - 55
    )

  }

  if (robotState == 1) {

    canvas.fill(
      255,
      220,
      0
    )

    canvas.text(
      "SCANNING ROCK...",
      25,
      cheight - 55
    )

  }

  if (robotState == 2) {

    canvas.fill(
      0,
      220,
      255
    )

    canvas.text(
      "COLLECTING MARS SAMPLE...",
      25,
      cheight - 55
    )

  }

  if (robotState == 3) {

    canvas.fill(
      255,
      150,
      20
    )

    canvas.text(
      "WELDING / REPAIRING...",
      25,
      cheight - 55
    )

  }

  // Energy bar

  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,
    energy * 1.35,
    10
  )

  canvas.fill(
    255,
    255,
    255
  )

  // IMPORTANT:
  // energy is already Int.
  // NO energy.toInt()

  canvas.text(
    "ENERGY " + energy + "%",
    cwidth - 180,
    cheight - 72
  )

  // Samples

  canvas.fill(
    255,
    220,
    80
  )

  canvas.text(
    "SAMPLES: " + samples,
    cwidth - 180,
    cheight - 100
  )

  // Score

  canvas.fill(
    100,
    220,
    255
  )

  canvas.text(
    "SCORE: " + score,
    cwidth - 180,
    cheight - 125
  )

  // Controls

  canvas.fill(
    230,
    230,
    230
  )

  canvas.text(
    "A/D or <- -> MOVE",
    20,
    35
  )

  canvas.text(
    "SPACE SCAN | E COLLECT | F WELD",
    20,
    52
  )

  canvas.text(
    "1 WALK | 2 SCAN | 3 COLLECT | 4 WELD | P PAUSE | R RESET",
    20,
    69
  )

  // Pause

  if (gamePaused) {

    canvas.fill(
      255,
      255,
      255
    )

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

  }

  // Mission complete

  if (missionComplete) {

    canvas.fill(
      0,
      255,
      120
    )

    canvas.text(
      "MISSION COMPLETE!",
      cwidth / 2 - 70,
      cheight / 2 + 50
    )

    canvas.fill(
      255,
      255,
      255
    )

    canvas.text(
      "MARS SAMPLES COLLECTED",
      cwidth / 2 - 80,
      cheight / 2 + 30
    )

  }

} //> def drawRobotHUD(canvas: builtins.CanvasDraw): Unit

// ============================================================
// MAIN VIEW
// ============================================================

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 GAME
// ============================================================

activateCanvas()

animateWithCanvasDraw { canvas =>

  updateRobot()

  viewRobot(canvas)

}