MakeCode Blocks · Beginner Guide

Turning Test
Program

Learn how this MakeCode program drives your bot through arc patterns, tests how the TURNING stat shapes movement, and watches for the border while it works.

Open in MakeCode

What does this program do?

This program tests how your bot moves in arcs based on your TURNING stat. While driving, it continuously watches the track sensors and recovers if it hits the border. You can change the arc style mid-session and simulate different points in a match to see how ENDURANCE affects speed.

The key idea: your TURNING stat doesn't just control whether you turn — it controls the shape of the pattern your bot traces around the ring. Testing it here tells you what to expect in a real match.

⚠️
Before running: Set SPEED, ENDURANCE, and TURNING in the on start block to your character's actual stats. All three start at zero — leave them there and the bot won't move.
🔄
Button roles changed from the Endurance Test. Button B now cycles the simulated match time (0 → 60 → 120 → 180 → 0s). Button A+B is the emergency stop — press both buttons together to cut motors immediately. Set the TURNING value in the on start block before uploading.

Three buttons. Different roles than before.

A

Run the Test

Calculates power and arc settings from your stats, then drives the full arc pattern for TEST_DURATION_MS. Watch the shape it traces and how it reacts at the border.

B

Cycle Simulated Match Time

Cycles through 0 → 60 → 120 → 180 → 0 seconds. Shows the time as text on the LED screen. Use this to simulate how endurance decay affects the arc at different points in a match.

A+B

Emergency Stop

Press both buttons together to cut motors immediately and show an X on the LED screen. Use this whenever the bot heads somewhere it shouldn't.

How each section works

on start — Setting Up Variables
Loading blocks from MakeCode…
let TURNING = 0
let TEST_DURATION_MS = 20000
let BORDER_CHECK_MS = 60
let BORDER_PIVOT_MS = 400
let BORDER_PIVOT_POWER = 50
let BACKUP_MS = 600
let BASE_SEGMENT_MS = 1800
let BORDER_THRESHOLD = 200
let SPEED = 0
let ENDURANCE = 0
          

Runs once when the micro:bit powers on. Fills in all the starting values before any button is pressed.

1
TURNING = 0 — Fill this in with your character's actual stat before uploading. Try 2 for a gentle arc pattern, 5 for medium, or 10 for the sharpest possible.
2
TEST_DURATION_MS = 20000 — Each test runs for 20 seconds (20,000ms). Increase this for longer observation time.
3
BASE_SEGMENT_MS = 1800 — New in this program. Controls the "base length" of each arc segment before it's divided by TURNING. 1800ms × 12 = 21.6 seconds of total arc time per cycle.
4
SPEED = 0, ENDURANCE = 0, and TURNING = 0 — Fill these in with your character's actual stats before uploading. All three start at zero — leave them and the bot won't move.
on button A pressed — Run the Arc Test
Loading blocks from MakeCode…
let TURNING = 0
let TEST_DURATION_MS = 0
let currentPower = 0
let turnRatio = 0
let thisDuration = 0
let watchResult = 0
let running = false
let testStart = 0
let lastTurnWasLeft = false
function computeStats() {}
function driveAndWatch(ms: number) {}
function handleBorder() {}
input.onButtonPressed(Button.A, function () {
    computeStats()
    basic.showString("" + (TURNING))
    running = true
    testStart = input.runningTime()
    while (running && input.runningTime() - testStart < TEST_DURATION_MS) {
        lastTurnWasLeft = !(lastTurnWasLeft)
        for (let index = 0; index < TURNING; index++) {
            if (input.runningTime() - testStart >= TEST_DURATION_MS) {
                running = false
                break
            }
            if (TURNING == 1) {
                CutebotPro.pwmCruiseControl(currentPower, currentPower)
            } else if (lastTurnWasLeft) {
                CutebotPro.pwmCruiseControl(currentPower * (1 - turnRatio), currentPower * (1 + turnRatio))
            } else {
                CutebotPro.pwmCruiseControl(currentPower * (1 + turnRatio), currentPower * (1 - turnRatio))
            }
            driveAndWatch(thisDuration)
            CutebotPro.stopImmediately(CutebotProMotors.ALL)
            if (watchResult == 1) {
                handleBorder()
                break
            }
            basic.pause(80)
        }
    }
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    running = false
    basic.showLeds(`
        # . . . #
        . # . # .
        . . # . .
        . # . # .
        # . . . #
        `)
    basic.pause(500)
    basic.clearScreen()
})
          

The main test loop. This is the most complex block in the program — it has a loop inside a loop. Here's how to read it:

1
call computeStats → calculates currentPower, turnRatio, and thisDuration from your stats. Then displays the current TURNING value on screen.
2
outer while loop — keeps running until time expires. Each iteration: flip direction (lastTurnWasLeft), then run one full set of TURNING segments.
3
inner for loop — runs TURNING times. Each pass checks if time is up, sets wheel speeds based on the current direction, calls driveAndWatch, then pauses 80ms between segments.
4
Wheel speed logic: If lastTurnWasLeft is true → left wheel slower, right wheel faster (arcs left). If false → left faster, right slower (arcs right). The difference is controlled by turnRatio.
5
If driveAndWatch sets watchResult = 1 (border hit), call handleBorder and break out of the inner loop to restart the outer loop from a fresh direction.
6
When time expires: stop all motors, show an ✕ on the LED screen, then clear it.
on button B pressed — Cycle Simulated Match Time
Loading blocks from MakeCode…
let simulatedMatchTime = 0
input.onButtonPressed(Button.B, function () {
    if (simulatedMatchTime == 0) {
        simulatedMatchTime = 60
        basic.showString("60s")
    } else if (simulatedMatchTime == 60) {
        simulatedMatchTime = 120
        basic.showString("120s")
    } else if (simulatedMatchTime == 120) {
        simulatedMatchTime = 180
        basic.showString("180s")
    } else {
        simulatedMatchTime = 0
        basic.showString("0s")
    }
    basic.pause(500)
    basic.clearScreen()
})
          

Cycles the simulated match time: 0 → 60 → 120 → 180 → 0. The current time shows as text on the LED screen (e.g. "60s"). This feeds into computeStats to apply endurance decay — so you can feel how a tired bot arcs differently at the end of a match.

✅
Set time before pressing A. Press B to advance the simulated match time, then press A to run with that decay applied. Compare 0s vs. 180s with the same TURNING value to see how a tired bot's arc shape changes.
on button A+B pressed — Emergency Stop
Loading blocks from MakeCode…
let running = false
input.onButtonPressed(Button.AB, function () {
    running = false
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    basic.showLeds(`
        # . . . #
        . # . # .
        . . # . .
        . # . # .
        # . . . #
        `)
    basic.pause(500)
    basic.clearScreen()
})
          

Press both buttons together to cut motors immediately. Sets running = false so any in-progress test loop exits, calls stopImmediately to kill power to both wheels, and flashes an X on the LED screen to confirm the stop.

🛑
Use A+B whenever the bot heads somewhere it shouldn't. The X is your signal that motors are off. After a stop, press A to start a new test from the beginning.
function computeStats — Calculating Power and Arc Shape
Loading blocks from MakeCode…
let SPEED = 0
let ENDURANCE = 0
let TURNING = 0
let BASE_SEGMENT_MS = 0
let simulatedMatchTime = 0
let currentPower = 0
let totalArcTime = 0
let thisDuration = 0
let turnRatio = 0
let DECAY_RATE = 0
let endurancePercent = 0
let enduranceFactor = 0
function computeStats () {
    currentPower = SPEED * 100 / 20
    totalArcTime = BASE_SEGMENT_MS * 12
    thisDuration = Math.round(totalArcTime / TURNING)
    turnRatio = 0.2 + TURNING * 0.02
    turnRatio = Math.min(turnRatio, 0.4)
    DECAY_RATE = 20 - ENDURANCE
    endurancePercent = 100 - DECAY_RATE * simulatedMatchTime / 40
    endurancePercent = Math.max(endurancePercent, 20)
    enduranceFactor = endurancePercent / 100
    currentPower = Math.round(currentPower * enduranceFactor)
    currentPower = Math.max(currentPower, 15)
}
          

This function calculates everything before any motor turns. It does two jobs: sets the arc shape (turnRatio and thisDuration) and applies endurance decay to currentPower.

currentPower = SPEED × 100 ÷ 20 ← converts stat to motor %
totalArcTime = BASE_SEGMENT_MS × 12 ← 1800 × 12 = 21,600ms total
thisDuration = totalArcTime ÷ TURNING ← time per segment
turnRatio = 0.2 + TURNING × 0.02 ← arc sharpness (max 0.40)
currentPower × enduranceFactor ← decay applied last
📐
thisDuration example: With TURNING = 5 → 21600 ÷ 5 = 4320ms per segment. With TURNING = 10 → 21600 ÷ 10 = 2160ms per segment. More segments = shorter each.
〰️
turnRatio example: TURNING = 2 → ratio = 0.24 (gentle arc). TURNING = 10 → ratio = 0.40 (capped, sharpest possible). The cap prevents the wheels from running backward.
⬇️
Endurance decay: Same math as the Endurance Test — power fades based on ENDURANCE stat and simulatedMatchTime. A bot with low ENDURANCE will arc more slowly at 180s than at 0s.
function driveAndWatch — Drive + Monitor Sensors Simultaneously
Loading blocks from MakeCode…
let watchResult = 0
let elapsed = 0
let BORDER_CHECK_MS = 0
let halfChunk = 0
let borderSide = 0
let remainChunk = 0
function checkBorder() { return 0 }
function driveAndWatch (ms: number) {
    watchResult = 0
    elapsed = 0
    while (elapsed < ms) {
        halfChunk = Math.min(BORDER_CHECK_MS, ms - elapsed)
        basic.pause(halfChunk)
        elapsed += halfChunk
        borderSide = checkBorder()
        if (borderSide > 0) {
            CutebotPro.stopImmediately(CutebotProMotors.ALL)
            watchResult = 1
            return
        }
        if (elapsed >= ms) {
            return
        }
        remainChunk = Math.min(BORDER_CHECK_MS, ms - elapsed)
        basic.pause(remainChunk)
        elapsed += remainChunk
        borderSide = checkBorder()
        if (borderSide > 0) {
            CutebotPro.stopImmediately(CutebotProMotors.ALL)
            watchResult = 1
            return
        }
    }
}
          

This is a new function not in the Endurance Test. Instead of driving for a full segment and then checking the sensors, this function checks the sensors during the drive — up to twice per 60ms window. That means the bot can react to the border in the middle of a segment rather than waiting until it's over.

1
Sets watchResult = 0 (no border) and elapsed = 0 (time used so far). The motors are already running — the calling code set wheel speeds before calling this function.
2
while loop — keeps going until elapsed ≥ ms. Each pass: wait a small chunk of time, add it to elapsed, check sensors. Two sensor checks per iteration for faster reaction.
3
halfChunk = Math.min(BORDER_CHECK_MS, ms − elapsed) — this prevents overshooting the target time. If less than 60ms remains, it waits only the remainder, not a full 60ms.
4
If checkBorder() returns anything other than 0 → set watchResult = 1 and return immediately. The calling code (Button A loop) sees watchResult = 1 and calls handleBorder.
💡
Why not just pause the full segment? If the bot drove for 4,320ms without checking sensors, it could go deep into the white border before reacting. This function checks every 60ms, so at full speed it stops within 1–2 sensor cycles of hitting the edge.
function checkBorder — Reading the Sensors
Loading blocks from MakeCode…
let sensor1State = 0
let sensor2State = 0
let sensor3State = 0
let sensor4State = 0
let BORDER_THRESHOLD = 0
function checkBorder () {
    CutebotPro.trackbitStateValue()
    sensor1State = CutebotPro.trackbitgetGray(TrackbitChannel.One)
    sensor2State = CutebotPro.trackbitgetGray(TrackbitChannel.Two)
    sensor3State = CutebotPro.trackbitgetGray(TrackbitChannel.Three)
    sensor4State = CutebotPro.trackbitgetGray(TrackbitChannel.Four)
    if (sensor1State > BORDER_THRESHOLD) {
        return 1
    } else if (sensor4State > BORDER_THRESHOLD) {
        return 2
    } else if (sensor2State > BORDER_THRESHOLD || sensor3State > BORDER_THRESHOLD) {
        return 3
    }
    return 0
}
          

Same as the Endurance Test — reads four sensors and returns a number based on which side (if any) detected the white border.

↑ Front of robot
S1left
S2mid
S3mid
S4right
BOT BODY
↓ Back of robot
S1 fires → returns 1 (left)
S4 fires → returns 2 (right)
S2 or S3 → returns 3 (head-on)
All clear → returns 0
function handleBorder — Recovering from the Edge
Loading blocks from MakeCode…
let BORDER_PIVOT_POWER = 0
let BACKUP_MS = 0
let BORDER_PIVOT_MS = 0
let borderSide = 0
let lastTurnWasLeft = false
let recheck = 0
function checkBorder() { return 0 }
function handleBorder () {
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    basic.pause(50)
    CutebotPro.pwmCruiseControl(-1 * BORDER_PIVOT_POWER, -1 * BORDER_PIVOT_POWER)
    basic.pause(BACKUP_MS)
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    basic.pause(100)
    if (borderSide == 1) {
        CutebotPro.pwmCruiseControl(BORDER_PIVOT_POWER, -1 * BORDER_PIVOT_POWER)
        basic.pause(BORDER_PIVOT_MS + Math.randomRange(-80, 80))
        lastTurnWasLeft = false
    } else if (borderSide == 2) {
        CutebotPro.pwmCruiseControl(-1 * BORDER_PIVOT_POWER, BORDER_PIVOT_POWER)
        basic.pause(BORDER_PIVOT_MS + Math.randomRange(-80, 80))
        lastTurnWasLeft = true
    } else {
        CutebotPro.pwmCruiseControl(-1 * BORDER_PIVOT_POWER, -1 * BORDER_PIVOT_POWER)
        basic.pause(BACKUP_MS + 300)
        CutebotPro.stopImmediately(CutebotProMotors.ALL)
        basic.pause(100)
        CutebotPro.pwmCruiseControl(BORDER_PIVOT_POWER, -1 * BORDER_PIVOT_POWER)
        basic.pause(BORDER_PIVOT_MS * 2)
        lastTurnWasLeft = false
    }
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    basic.pause(100)
    recheck = checkBorder()
    if (recheck > 0) {
        borderSide = recheck
        CutebotPro.pwmCruiseControl(-1 * BORDER_PIVOT_POWER, -1 * BORDER_PIVOT_POWER)
        basic.pause(BACKUP_MS)
        CutebotPro.stopImmediately(CutebotProMotors.ALL)
        basic.pause(100)
        CutebotPro.pwmCruiseControl(-1 * BORDER_PIVOT_POWER, BORDER_PIVOT_POWER)
        basic.pause(BORDER_PIVOT_MS)
        CutebotPro.stopImmediately(CutebotProMotors.ALL)
        basic.pause(100)
    }
}
          

Same recovery sequence as the Endurance Test — stop, back up, pivot based on which side hit the border. One new addition in this version: lastTurnWasLeft gets set based on the recovery direction, so the arc pattern resumes with the correct orientation after a border event.

1
Stop and back up for BACKUP_MS (600ms) to clear the border zone.
2
Pivot away based on borderSide. Left danger → pivot right. Right danger → pivot left. Head-on → extra backup + bigger turn.
3
Reset arc direction: After recovering left (pivoting right), set lastTurnWasLeft = false so the next segment arcs right — away from the side that was just hit.
4
Recheck — if still on white, does one more backup-and-turn before resuming the main loop.

What every variable means

Gold = you set it. Cyan = calculated automatically. Purple = fixed constant. Green = new in this program.

SPEED You Set
Your character's speed statSets the base motor power. Used to calculate currentPower before decay is applied.
ENDURANCE You Set
Your character's endurance statControls how much motor power fades over simulated match time. Low ENDURANCE = noticeable slowdown at 180s.
TURNING You Set
Number of arc segments per driving cycleSet in the on start block before uploading. Try 2, 5, or 10. Controls both the segment count and the arc sharpness (via turnRatio).
turnRatio New
Wheel speed difference that creates the arcCalculated as 0.2 + TURNING × 0.02, capped at 0.40. The higher this is, the tighter each arc curves.
thisDuration New
How long each individual arc segment lasts (ms)Calculated as totalArcTime ÷ TURNING. More segments = shorter each.
totalArcTime New
Total time budget for one full arc cycle (ms)Always BASE_SEGMENT_MS × 12 = 21,600ms. Divided among all segments to get thisDuration.
lastTurnWasLeft New
Tracks which direction the last arc wentFlips each outer loop iteration so arcs alternate left/right. Also reset by handleBorder to resume in the correct direction after recovery.
watchResult New
Output from driveAndWatch0 = segment completed cleanly. 1 = border was hit mid-segment. The main loop checks this after each call to driveAndWatch.
currentPower Auto
Actual motor power sent to wheels (0–100)Calculated from SPEED, then reduced by endurance decay. Minimum is 15 in this program (slightly different from the Endurance Test's 20% floor).
simulatedMatchTime Auto
Fake "time into the match" for endurance testingSet by pressing Button B. 0 = start, 180 = near the end. Passed to computeStats to apply decay.
BASE_SEGMENT_MS Constant
Base arc segment length before dividing by TURNINGSet to 1800ms. Change this to make all segments longer or shorter without touching TURNING.
BORDER_THRESHOLD Constant
Sensor brightness cutoff for border detectionSet to 200. Sensor readings above this = white border detected.

Tips for using this program

01

Set stats before uploading

Find SPEED = 0, ENDURANCE = 0, and TURNING = 0 in the on start block. Replace all three zeros with your character's actual values — leave any at zero and nothing will move.

02

Try all three TURNING values

Run with TURNING = 2, 5, and 10 in sequence. Watch the floor pattern each one traces. The difference is especially visible at high SPEED stats.

03

Combine TURNING + match time

Set match time to 180s with Button B, then run your chosen TURNING value. Compare to 0s. If the arcs are noticeably tighter or slower, ENDURANCE is affecting your arc style.

04

Watch the border recovery

Use a longer TEST_DURATION_MS (e.g. 30000 = 30s) to give the bot more time to hit the border and recover. See how handleBorder adjusts the arc direction after each incident.

05

Adjust BASE_SEGMENT_MS

Lowering BASE_SEGMENT_MS makes all segments shorter. Raising it makes them longer. Try 900 for half-length arcs or 3600 for double — observe how this changes coverage without touching TURNING.

06

Keep A+B within reach

A+B is the emergency stop. Stay close to the bot, and press both buttons together the moment it heads somewhere it shouldn't. The X on the LED screen confirms motors are off.