← Resources skillsCode-SOLUTION.ts Download .ts

Teacher copy · Velocity Arena

Skills Arena — Reference Code

The reference solution for every Skills Arena challenge. Several challenges have more than one valid route: if a student's code lands the bot correctly, it is right, even if it does not match this. Use it to unstick a stuck team, not to grade.

Do not project this on the board. This is the answer. Read it yourself, then go help the team with a question — do not show them the screen.
507 lines · source file skillsCode-SOLUTION.ts
/**
 * ═══════════════════════════════════════════════════════════════
 *  VELOCITY ARENA — Skills Arena  ★ SOLUTION FILE ★
 *  micro:bit + Cutebot Pro   |   8×4 ft field   |   8×4 grid
 *
 *  TEACHER / FACILITATOR USE ONLY
 *  Do not distribute to students before they attempt the
 *  challenges independently.
 *
 *  All six challenge functions are fully implemented below.
 *  Stats are set to a balanced example build (S6·P5·E5·T4 = 20).
 *  Swap in any valid build to test with different stat combos.
 * ═══════════════════════════════════════════════════════════════
 *
 *  TURN DIRECTION REFERENCE
 *  ─────────────────────────
 *  turnRight()  rotates:  south→east, east→north, north→west, west→south
 *  turnLeft()   rotates:  south→west, west→north, north→east, east→south
 *
 *  Memory trick: imagine a compass rose — turnRight() spins the
 *  needle one step COUNTER-clockwise (south→east→north→west).
 *  turnLeft() spins it CLOCKWISE (south→west→north→east).
 *
 *  FIELD COORDINATE SYSTEM
 *  ────────────────────────
 *  x: 0 (left wall) → 8 (right wall)       8 columns
 *  y: 0 (top edge)  → 4 (bottom edge)      4 rows
 *  Left  goal zone: x = 0–1, y = 1–3
 *  Right goal zone: x = 7–8, y = 1–3
 * ═══════════════════════════════════════════════════════════════
 */


// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//  EXAMPLE STATS  (S + P + E + T = 20)
//  Swap these values to test different builds.
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
let SPEED     = 6   // motorPower  = 30%
let POWER     = 5   // chargePower = 25%
let ENDURANCE = 5   // DECAY_RATE  = 15
let TURNING   = 4   // alignMs     = 156 ms
// 6 + 5 + 5 + 4 = 20 ✓

// ── CALCULATED VALUES ──────────────────────────────────────────
let motorPower  = SPEED * 100 / 20             // drive speed %   (0–85)
let chargePower = Math.min(POWER * POWER, 100) // kick force %    (0–100)
let alignMs     = Math.max(60, 220 - TURNING * 16) // aim time ms
let DECAY_RATE  = 20 - ENDURANCE               // endurance decay rate


// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//  DRIVE TUNING
//  unitMs = ms per 1 grid square at your motorPower.
//  Tune using challenge 1 (Dead Straight):
//    Stopped short? → increase   |   Overshot? → decrease
//
//  NOTE: This example value is approximate for SPEED = 6 (motorPower 30%).
//  Every robot is slightly different — students must tune their own.
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
let unitMs = 500   // example starting point — tune via Dead Straight


// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//  CHALLENGE SELECTOR
//  B = next challenge (1 → 2 → … → 10 → 1 → ...)
//  A = run the selected challenge
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
let currentChallenge = 1

input.onButtonPressed(Button.B, function () {
    currentChallenge = (currentChallenge % 10) + 1
    basic.showNumber(currentChallenge)
})

input.onButtonPressed(Button.A, function () {
    basic.showArrow(ArrowNames.East)
    if      (currentChallenge === 1) { deadStraight()    }
    else if (currentChallenge === 2) { rightAngle()      }
    else if (currentChallenge === 3) { straightShot()    }
    else if (currentChallenge === 4) { cornerKickLeft()  }
    else if (currentChallenge === 5) { cornerKickRight() }
    else if (currentChallenge === 6) { lawnmower()       }
    else if (currentChallenge === 7) { twoBallCombo()    }
    else if (currentChallenge === 8) { powerControlLab() }
    else if (currentChallenge === 9) { precisionParking() }
    else if (currentChallenge === 10){ bossLevel()        }
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    basic.showIcon(IconNames.Yes)
})

// A + B  →  emergency stop
input.onButtonPressed(Button.AB, function () {
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    CutebotPro.pwmCruiseControl(0, 0)
    basic.showLeds(`
        # . . . #
        . # . # .
        . . # . .
        . # . # .
        # . . . #`)
})


// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//  HELPER FUNCTIONS
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

function drive(units: number): void {
    CutebotPro.pwmCruiseControl(motorPower, motorPower)
    basic.pause(unitMs * units)
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    basic.pause(100)
}

function turnRight(): void {
    CutebotPro.trolleySteering(CutebotProTurn.RightInPlace, 90)
    basic.pause(150)
}

function turnLeft(): void {
    CutebotPro.trolleySteering(CutebotProTurn.LeftInPlace, 90)
    basic.pause(150)
}

function kick(): void {
    CutebotPro.pwmCruiseControl(chargePower, chargePower)
    basic.pause(400)
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
}


// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//  ★  SOLUTIONS  ★
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


// ── Challenge 1 — DEAD STRAIGHT ──────────────────────────────────
//
//  Start:  (1, 0)  facing east (+x)
//  Target: (7, 0)
//
//  Spatial reasoning:
//    Δx = 7 − 1 = 6 grid squares to travel.
//    No direction change needed — already facing the target.
//
//  Path:   (1,0) ──── drive 6 ───→ (7,0)
//
function deadStraight(): void {
    drive(6)
}


// ── Challenge 2 — RIGHT ANGLE ─────────────────────────────────────
//
//  Start:  (1, 0)  facing south (+y, down the field)
//  Target: (6, 3)
//
//  Spatial reasoning:
//    Leg 1 (south): from (1,0) to (1,3) → Δy = 3 squares.
//    Turn: facing south → turnRight() → now facing east (+x).
//    Leg 2 (east):  from (1,3) to (6,3) → Δx = 5 squares.
//
//  Path:   (1,0)
//            │ drive 3
//          (1,3) ── turnRight ──→ facing east
//            └─── drive 5 ───→ (6,3)
//
function rightAngle(): void {
    drive(3)      // south: (1,0) → (1,3)
    turnRight()   // south → east
    drive(5)      // east:  (1,3) → (6,3)
}


// ── Challenge 3 — STRAIGHT SHOT ──────────────────────────────────
//
//  Start:  (6, 2)  facing west (−x, toward left goal)
//  Ball:   (4, 2)
//  Goal:   left goal zone (x = 0–1, y = 1–3)
//
//  Spatial reasoning:
//    Bot and ball are already on the same row (y = 2).
//    Bot is to the RIGHT of the ball and facing left — perfectly
//    aligned for a straight approach.
//    Δx = 6 − 4 = 2 squares to the ball.
//    After drive(2) the bot is AT (4,2), still facing west.
//    kick() fires west; the ball travels 4+ more squares into
//    the left goal zone (x = 0–1).
//
//  Path:   (6,2) ─ drive 2 → (4,2)[ball] ─ kick → left goal
//
function straightShot(): void {
    drive(2)   // west: (6,2) → (4,2), contacting ball
    kick()     // blast west toward left goal
}


// ── Challenge 4 — CORNER KICK: LEFT SIDE ─────────────────────────
//
//  Start:  (3, 0)  facing south (+y)
//  Ball:   (1, 2)
//  Goal:   left goal zone (x = 0–1, y = 1–3)
//
//  Spatial reasoning:
//    Leg 1 (south): from (3,0) to (3,2) → Δy = 2 squares.
//    Turn: facing south → turnLeft() → now facing west (−x).
//    Leg 2 (west):  from (3,2) to (1,2) → Δx = 2 squares.
//    Bot arrives at (1,2) facing west — lined up with left goal.
//    kick() fires west straight into the goal.
//
//  Turn rule: from south, turnLeft() faces west (−x = left wall).
//
//  Path:   (3,0)
//            │ drive 2
//          (3,2) ── turnLeft ──→ facing west
//            └─── drive 2 ───→ (1,2)[ball] ─ kick → left goal
//
function cornerKickLeft(): void {
    drive(2)     // south: (3,0) → (3,2)
    turnLeft()   // south → west
    drive(2)     // west:  (3,2) → (1,2), contacting ball
    kick()       // blast west into left goal
}


// ── Challenge 5 — CORNER KICK: RIGHT SIDE ────────────────────────
//
//  Start:  (4, 0)  facing south (+y)
//  Ball:   (6, 2)
//  Goal:   right goal zone (x = 7–8, y = 1–3)
//
//  Spatial reasoning:
//    Leg 1 (south): from (4,0) to (4,2) → Δy = 2 squares.
//    Turn: facing south → turnRight() → now facing east (+x).
//    Leg 2 (east):  from (4,2) to (6,2) → Δx = 2 squares.
//    Bot arrives at (6,2) facing east — lined up with right goal.
//    kick() fires east straight into the goal.
//
//  Turn rule: from south, turnRight() faces east (+x = right wall).
//
//  Path:   (4,0)
//            │ drive 2
//          (4,2) ── turnRight ──→ facing east
//            └─── drive 2 ───→ (6,2)[ball] ─ kick → right goal
//
function cornerKickRight(): void {
    drive(2)      // south: (4,0) → (4,2)
    turnRight()   // south → east
    drive(2)      // east:  (4,2) → (6,2), contacting ball
    kick()        // blast east into right goal
}


// ── Challenge 6 — THE LAWNMOWER ───────────────────────────────────
//
//  Start:  (0, 0)  facing south (+y)
//  Goal:   sweep every row — 8 straight segments, 7 turns.
//
//  Full path trace (facing shown after each turn):
//
//  Seg 1:  south  drive(4)   (0,0)→(0,4)
//  Turn 1: turnRight()       south → east
//  Seg 2:  east   drive(8)   (0,4)→(8,4)
//  Turn 2: turnRight()       east  → north
//  Seg 3:  north  drive(1)   (8,4)→(8,3)
//  Turn 3: turnRight()       north → west
//  Seg 4:  west   drive(8)   (8,3)→(0,3)
//  Turn 4: turnLeft()        west  → north
//  Seg 5:  north  drive(1)   (0,3)→(0,2)
//  Turn 5: turnLeft()        north → east
//  Seg 6:  east   drive(8)   (0,2)→(8,2)
//  Turn 6: turnRight()       east  → north
//  Seg 7:  north  drive(1)   (8,2)→(8,1)
//  Turn 7: turnRight()       north → west
//  Seg 8:  west   drive(8)   (8,1)→(0,1)
//
//  Why turnLeft() on turns 4 and 5?
//    After sweeping right-to-left (west), the bot needs to step
//    UP one row (north) then turn back east.  From west, a
//    turnLeft() points north; from north, another turnLeft()
//    points east.  This keeps the snaking pattern consistent.
//
//  Turn direction table (for reference):
//    turnRight():  south→east, east→north, north→west, west→south
//    turnLeft():   south→west, west→north, north→east, east→south
//
function lawnmower(): void {
    // ── Row sweep: y = 0 → 4 (south, left edge) ──
    drive(4)      // (0,0) → (0,4)   facing south
    turnRight()   // south → east

    // ── Row sweep: y = 4 (east, full width) ──
    drive(8)      // (0,4) → (8,4)   facing east
    turnRight()   // east → north

    // ── Step up one row ──
    drive(1)      // (8,4) → (8,3)   facing north
    turnRight()   // north → west

    // ── Row sweep: y = 3 (west, full width) ──
    drive(8)      // (8,3) → (0,3)   facing west
    turnLeft()    // west → north

    // ── Step up one row ──
    drive(1)      // (0,3) → (0,2)   facing north
    turnLeft()    // north → east

    // ── Row sweep: y = 2 (east, full width) ──
    drive(8)      // (0,2) → (8,2)   facing east
    turnRight()   // east → north

    // ── Step up one row ──
    drive(1)      // (8,2) → (8,1)   facing north
    turnRight()   // north → west

    // ── Row sweep: y = 1 (west, full width) ──
    drive(8)      // (8,1) → (0,1)   facing west
    // done — 8 segments, 7 turns, all 4 rows covered
}


// ── Challenge 7 — TWO-BALL COMBO ─────────────────────────────────
//
//  Start:  (4, 0)  facing south (+y)
//  Ball A: (2, 2)  → left goal zone  (x = 0–1, y = 1–3)
//  Ball B: (6, 2)  → right goal zone (x = 7–8, y = 1–3)
//
//  This challenge has no single correct route. Two valid paths:
//
//  ── Path A: kick Ball A first ────────────────────────────────────
//
//    drive(2)       south: (4,0) → (4,2)   facing south
//    turnLeft()     south → west
//    drive(2)       west: (4,2) → (2,2)    facing west  [Ball A]
//    kick()         Ball A fires west → left goal ✓
//    turnRight()    west → south
//    turnRight()    south → east
//    drive(4)       east: (2,2) → (6,2)    facing east  [Ball B]
//    kick()         Ball B fires east → right goal ✓
//
//  ── Path B: kick Ball B first (mirror of A) ──────────────────────
//
//    drive(2)       south: (4,0) → (4,2)   facing south
//    turnRight()    south → east
//    drive(2)       east: (4,2) → (6,2)    facing east  [Ball B]
//    kick()         Ball B fires east → right goal ✓
//    turnLeft()     east → south
//    turnLeft()     south → west
//    drive(4)       west: (6,2) → (2,2)    facing west  [Ball A]
//    kick()         Ball A fires west → left goal ✓
//
//  Turn direction reference:
//    turnRight():  south→east, east→north, north→west, west→south
//    turnLeft():   south→west, west→north, north→east, east→south
//
function twoBallCombo(): void {
    // ── PATH A: Ball A first ──────────────────────────────────
    drive(2)       // south: (4,0) → (4,2)
    turnLeft()     // south → west
    drive(2)       // west: (4,2) → (2,2), contacts Ball A
    kick()         // Ball A fires west → left goal

    turnRight()    // west → south
    turnRight()    // south → east
    drive(4)       // east: (2,2) → (6,2), contacts Ball B
    kick()         // Ball B fires east → right goal
}


// ── Challenge 8 — POWER CONTROL LAB ──────────────────────────────
//
//  Start:  (3, 2)  facing west (toward x = 0)
//  Ball:   (2, 2)  — reset to this position before every run
//  Goal:   Left goal zone (x = 0–1)  — ball fires west
//
//  Run 3 times, changing only POWER each time:
//    Run 1: POWER = 3  →  chargePower =  9%
//    Run 2: POWER = 6  →  chargePower = 36%
//    Run 3: POWER = 9  →  chargePower = 81%
//
//  Expected observation:
//    The distance increase is NOT linear. The chargePower values
//    increase by 27% (3→6) then by 45% (6→9). Ball distance
//    should also jump more on the second step than the first.
//    This is because chargePower = POWER² — a quadratic, not
//    linear, function of POWER.
//
//  Code is identical each run — only POWER in STUDENT ZONE A
//  changes. Re-flash after every POWER change.
//
function powerControlLab(): void {
    drive(1)   // west: (3,2) → (2,2), contacting the ball
    kick()     // fires west at chargePower = POWER²
}


// ── Challenge 9 — PRECISION PARKING ──────────────────────────────
//
//  Start:  (0, 0)  facing east (+x)
//  Target: taped box at (5, 3), final facing = WEST
//
//  Spatial reasoning:
//    To stop at (5,3) facing WEST, we need to arrive from the
//    east side (driving west into the box), OR arrive from
//    another direction and turn to face west.
//
//  ── Clean 4-call solution (approach from north) ───────────────
//
//    drive(5)     east:  (0,0) → (5,0)   facing east
//    turnLeft()   east → south            now facing south
//    drive(3)     south: (5,0) → (5,3)   facing south  [AT BOX]
//    turnLeft()   south → west            now facing west ✓
//
//  Note: the bot STOPS after the final turnLeft() — it is
//  already at (5,3). No extra drive() needed.
//
//  ── Alternative 6-call solution (approach from west) ──────────
//
//    turnLeft()   east → south
//    drive(3)     south: (0,0) → (0,3)   facing south
//    turnRight()  south → east
//    drive(5)     east: (0,3) → (5,3)    facing east  [AT BOX]
//    turnRight()  east → north
//    turnRight()  north → west            facing west ✓
//
function precisionParking(): void {
    drive(5)     // east:  (0,0) → (5,0)
    turnLeft()   // east → south
    drive(3)     // south: (5,0) → (5,3)  [inside box]
    turnLeft()   // south → west  ✓  [done — facing west, inside box]
}


// ── Challenge 10 — BOSS LEVEL ─────────────────────────────────────
//
//  Start:  (4, 0)  facing south (+y)
//  Ball:   (4, 2)
//  Cone:   (4, 1)  — cannot drive through this coordinate
//  Parking zone: x = 0–1, y = 0  (top-left corner)
//
//  Strategy: detour left (west) around cone, kick into right goal
//  (easiest from east-facing approach), then return north to park.
//
//  ── Phase 1: Detour around cone (go left / west) ──────────────
//
//    turnLeft()    south → west
//    drive(1)      west: (4,0) → (3,0)
//    turnRight()   west → south
//    drive(2)      south: (3,0) → (3,2)
//
//  ── Phase 2: Reach ball at (4,2) ──────────────────────────────
//
//    turnRight()   south → east
//    drive(1)      east: (3,2) → (4,2)  [at ball, facing east]
//
//  ── Phase 3: Kick into right goal ─────────────────────────────
//
//    kick()        fires east → right goal (x=7–8) ✓
//
//  ── Phase 4: Drive to parking zone (x=0–1, y=0) ──────────────
//
//    turnRight()   east → north
//    drive(2)      north: (4,2) → (4,0)
//    turnRight()   north → west
//    drive(3)      west: (4,0) → (1,0)  [in parking zone ✓]
//
//  Full path trace:
//    (4,0) ─ L ─▶ west ─ d1 ─▶ (3,0)
//    ─ R ─▶ south ─ d2 ─▶ (3,2)
//    ─ R ─▶ east ─ d1 ─▶ (4,2) [ball] ─ kick ─▶ right goal
//    ─ R ─▶ north ─ d2 ─▶ (4,0)
//    ─ R ─▶ west ─ d3 ─▶ (1,0) [parked ✓]
//
function bossLevel(): void {
    // Phase 1: detour left around cone at (4,1)
    turnLeft()    // south → west
    drive(1)      // west: (4,0) → (3,0)
    turnRight()   // west → south
    drive(2)      // south: (3,0) → (3,2)

    // Phase 2: reach ball at (4,2)
    turnRight()   // south → east
    drive(1)      // east: (3,2) → (4,2) [contacts ball]

    // Phase 3: kick into right goal
    kick()        // fires east → right goal (x=7–8)

    // Phase 4: drive to parking zone
    turnRight()   // east → north
    drive(2)      // north: (4,2) → (4,0)
    turnRight()   // north → west
    drive(3)      // west: (4,0) → (1,0) [parking zone x=0–1 ✓]
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//  STARTUP
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CutebotPro.stopImmediately(CutebotProMotors.ALL)
CutebotPro.pwmCruiseControl(0, 0)
basic.pause(100)

basic.showString("P" + motorPower)
basic.pause(400)
basic.showString("K" + chargePower)
basic.pause(400)

basic.showNumber(currentChallenge)
Velocity Arena · NYC FIRST. Also see: Match code reference · How to Run Skills Arena.