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.
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.
Three buttons. Different roles than before.
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.
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.
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
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.
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.TEST_DURATION_MS = 20000 — Each test runs for 20 seconds (20,000ms). Increase this for longer observation time.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.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.
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:
currentPower, turnRatio, and thisDuration from your stats. Then displays the current TURNING value on screen.lastTurnWasLeft), then run one full set of TURNING segments.driveAndWatch, then pauses 80ms between segments.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.driveAndWatch sets watchResult = 1 (border hit), call handleBorder and break out of the inner loop to restart the outer loop from a fresh direction.
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.
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.
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.
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
21600 ÷ 5 = 4320ms per segment. With TURNING = 10 → 21600 ÷ 10 = 2160ms per segment. More segments = shorter each.
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.
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.elapsed ≥ ms. Each pass: wait a small chunk of time, add it to elapsed, check sensors. Two sensor checks per iteration for faster reaction.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.checkBorder() returns anything other than 0 → set watchResult = 1 and return immediately. The calling code (Button A loop) sees watchResult = 1 and calls handleBorder.
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.
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.
borderSide. Left danger → pivot right. Right danger → pivot left. Head-on → extra backup + bigger turn.lastTurnWasLeft = false so the next segment arcs right — away from the side that was just hit.What every variable means
Gold = you set it. Cyan = calculated automatically. Purple = fixed constant. Green = new in this program.
currentPower before decay is applied.0.2 + TURNING × 0.02, capped at 0.40. The higher this is, the tighter each arc curves.totalArcTime ÷ TURNING. More segments = shorter each.BASE_SEGMENT_MS × 12 = 21,600ms. Divided among all segments to get thisDuration.Tips for using this program
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.
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.
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.
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.
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.
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.