MakeCode Blocks · Guía para Principiantes

Programa de
Prueba de Turning

Aprende cómo este programa MakeCode conduce tu robot a través de patrones de arco, prueba cómo la estadística TURNING da forma al movimiento y vigila el borde mientras trabaja.

Abrir en MakeCode

¿Qué hace este programa?

Este programa prueba cómo tu robot vaga en arcos según tu estadística TURNING — y la lógica de vagabundeo aquí es idéntica al ciclo de roam del programa de partido. Lo que ves en el suelo durante esta prueba es lo que verás durante los partidos reales. Mientras conduce, el robot consulta continuamente los sensores en dos umbrales (impacto duro + advertencia suave), se recupera si toca el borde, y adapta su conducción a medida que ENDURANCE decae.

La idea clave: tu estadística TURNING no solo controla si giras — controla la forma del patrón que tu robot traza alrededor del ring. TURNING alto → segmentos más cortos y cerrados (se queda local). TURNING bajo → segmentos más largos y suaves (cubre más). Probarlo aquí te dice qué esperar en un partido real.

⚠️
Configura las estadísticas y el umbral del borde antes de ejecutar. Los valores predeterminados son SPEED = 6, ENDURANCE = 4, TURNING = 3 — modifícalos para que coincidan con tu personaje. Además: BORDER_THRESHOLD debe coincidir con el valor que tu programa de partido calibra, o el comportamiento del borde no será consistente entre prueba y partido.
🔄
Los roles de los botones, iguales a la Prueba de Endurance. A = ejecutar una prueba de vagabundeo de 15 segundos (úsalo para el Desafío de Cobertura). B = ciclar el tiempo simulado del partido (0 → 60 → 120 → 180), y muestra la potencia decaída resultante en pantalla. A+B = parada de emergencia.

Tres botones. Roles diferentes a los anteriores.

A

Ejecutar la Prueba de Vagabundeo

Calcula la potencia + configuración de arco a partir de tus estadísticas, luego vaga durante 15 segundos usando el mismo modelo de movimiento que el programa de partido. Observa el patrón en el suelo y la tira RGB — azul/verde = potencia completa, rojo = estado de baja potencia.

B

Ciclar Tiempo + Mostrar Potencia

Cicla entre 0 → 60 → 120 → 180 → 0 segundos. Muestra el tiempo, luego la potencia de rueda decaída resultante ("P38" para potencia = 38) para que puedas ver cómo cae la potencia de un robot cansado sin ejecutar una prueba.

A+B

Parada de Emergencia

Presiona ambos botones juntos para detener los motores al instante. Limpia la tira RGB y muestra una X en la pantalla LED. Úsalo siempre que el robot vaya hacia donde no debería.

¿Cómo funciona cada sección?

on start — Configuración de Variables
Cargando bloques desde MakeCode…
let SPEED = 6
let ENDURANCE = 4
let TURNING = 3
let TEST_DURATION_MS = 15000
let BASE_SEGMENT_MS = 300
let BORDER_THRESHOLD = 130
const BORDER_POLL_MS = 12
          

Se ejecuta una vez cuando el micro:bit se enciende. Los valores predeterminados ya funcionan — los cambias para comparar diferentes valores de TURNING, no para "llenar desde cero". Presiona A para ejecutar con los valores actuales.

1
SPEED = 6, ENDURANCE = 4, TURNING = 3 — Estadísticas iniciales por defecto. Edítalas para que coincidan con tu personaje, luego vuelve a flashear para comparar. El Desafío de Cobertura te pide específicamente correr con T = 2, 5 y 10.
2
TEST_DURATION_MS = 15000 — Cada prueba dura 15 segundos. Esto coincide con la duración del Desafío de Cobertura — suficiente tiempo para ver desarrollarse el patrón de arco y recuperarse de al menos un impacto con el borde.
3
BASE_SEGMENT_MS = 300 — La longitud base de cada segmento de arco, antes de que TURNING lo reduzca. Con TURNING = 3, cada segmento corre unos 100ms antes de que el robot considere un cambio de dirección.
4
BORDER_THRESHOLD = 130 — Lectura del sensor por encima de esto cuenta como impacto con el borde. Configúralo al valor que tu programa de partido calibra — debe coincidir con el programa de partido o el comportamiento del borde será inconsistente entre prueba y partido.
5
BORDER_POLL_MS = 12 — Cada cuánto el robot lee los sensores durante un segmento. 12ms significa aproximadamente 80 lecturas por segundo — suficientemente rápido para reaccionar antes de que el robot pase el borde blanco.
on button A pressed — Ejecutar la Prueba de Arco
Cargando bloques desde MakeCode…
input.onButtonPressed(Button.A, function () {
    computeStats()
    sameDirectionCount = 0
    maxSameDirection = Math.randomRange(1, 2)
    lastTurnWasLeft = false
    running = true
    testStart = input.runningTime()
    while (running && input.runningTime() - testStart < TEST_DURATION_MS) {
        roamStep()
    }
    motorStop()
    running = false
    strip.clear()
    basic.showString("DONE")
    basic.pause(500)
    basic.clearScreen()
})
          

El bucle principal se volvió mucho más corto. Toda la lógica de dibujado de arcos se movió a una nueva función llamada roamStep — el Botón A solo la llama una y otra vez hasta que se acabe el tiempo. Esta es la misma forma que el bucle de vagabundeo del programa de partido, así que lo que ves aquí es lo que verás en los partidos.

1
computeStats → calcula motorPower, turnRatio, thisDuration y la currentPower decaída por endurance a partir de tus estadísticas y el tiempo simulado actual del partido.
2
Configuración de dirección — reinicia el contador de dirección (sameDirectionCount = 0), elige cuántos segmentos mantener la misma dirección antes de cambiar (maxSameDirection es 1 o 2, aleatorio), y comienza arqueándose a la derecha (lastTurnWasLeft = false).
3
El bucle de vagabundeo — sigue llamando a roamStep hasta que alguien presione A+B (que establece running = false) o transcurran 15 segundos. Cada roamStep conduce un segmento de arco, sondea el borde, y puede cambiar de dirección o recuperarse de un borde.
4
Limpieza — cuando el bucle sale, detén completamente los motores, limpia la tira RGB y muestra "DONE" en la pantalla LED.
on button B pressed — Ciclar Tiempo Simulado del Partido
Cargando bloques desde MakeCode…
input.onButtonPressed(Button.B, function () {
    if (simulatedMatchTime == 0) { simulatedMatchTime = 60 }
    else if (simulatedMatchTime == 60) { simulatedMatchTime = 120 }
    else if (simulatedMatchTime == 120) { simulatedMatchTime = 180 }
    else { simulatedMatchTime = 0 }
    computeStats()
    basic.showString(simulatedMatchTime + "s")
    basic.pause(300)
    basic.showString("P" + currentPower)
    basic.pause(400)
    basic.clearScreen()
})
          

Cicla el tiempo simulado del partido: 0 → 60 → 120 → 180 → 0. Después de ciclar, esta versión también llama a computeStats y muestra la potencia decaída resultante en pantalla — así puedes ver cómo cae la potencia real de un robot cansado sin tener que ejecutar una prueba.

1
Ciclar el tiempo — avanza el tiempo simulado del partido al siguiente punto del ciclo.
2
Refrescar la potencia — llama a computeStats inmediatamente para que currentPower refleje el nuevo tiempo.
3
Mostrar ambos números — muestra el tiempo (ej. "120s"), luego la potencia decaída de las ruedas (ej. "P38"), para que puedas leer el efecto con una sola pulsación.
✅
Configura el tiempo antes de presionar A. Presiona B hasta ver el tiempo de partido que quieres, luego A para vagar con ese nivel de decaimiento. Compara 0s vs. 180s con el mismo valor de TURNING para sentir cómo el arco de un robot cansado pierde su mordida.
on button A+B pressed — Parada de Emergencia
Cargando bloques desde MakeCode…
input.onButtonPressed(Button.AB, function () {
    running = false
    motorStop()
    strip.clear()
    basic.showLeds(`
        # . . . #
        . # . # .
        . . # . .
        . # . # .
        # . . . #
        `)
    basic.pause(500)
    basic.clearScreen()
})
          

Presiona ambos botones juntos para detener los motores al instante. Pone running = false para que cualquier ciclo de vagabundeo en curso salga en el siguiente chequeo de seguridad, llama a motorStop para un corte limpio de las ruedas, limpia la tira RGB, y muestra una X en la pantalla LED para confirmar la parada.

🛑
Usa A+B siempre que el robot vaya hacia donde no debería. La X es tu señal de que los motores están apagados. Después de una parada, presiona A para iniciar una nueva prueba desde el principio.
function computeStats — Calcular Potencia y Forma del Arco
Cargando bloques desde MakeCode…
function computeStats() {
    motorPower = SPEED * 100 / 20
    turnRatio = 0.2 + TURNING * 0.02
    totalArcTime = BASE_SEGMENT_MS * 12
    thisDuration = Math.min(Math.round(totalArcTime / Math.max(1, TURNING)), 600)
    DECAY_RATE = 20 - ENDURANCE
    minWheelSpeed = Math.max(20, Math.round(motorPower * 0.25))
    lowPowerThreshold = minWheelSpeed

    endurancePercent = 100 - DECAY_RATE * simulatedMatchTime / 40
    enduranceFactor = Math.max(endurancePercent / 100, 0.2)
    currentPower = Math.max(Math.round(motorPower * enduranceFactor), minWheelSpeed)
    lowPowerActive = currentPower <= lowPowerThreshold
}
          

Esta función calcula todo antes de que cualquier motor gire — la forma del arco, la potencia decaída por endurance, y una bandera de "baja potencia" que el resto del código usa para cambiar a una conducción más suave cuando el robot está cansado.

motorPower = SPEED × 100 ÷ 20 ← potencia fresca (antes del decaimiento)
turnRatio = 0.2 + TURNING × 0.02 ← nitidez del arco, sin tope
thisDuration = totalArcTime ÷ TURNING ← limitado a 600ms
minWheelSpeed = max(20, motorPower × 0.25) ← piso
enduranceFactor = max(endurancePercent/100, 0.2) ← decaimiento, mín 20%
currentPower = max(motorPower × factor, minWheelSpeed)
lowPowerActive = currentPower ≤ lowPowerThreshold
📐
Ejemplo de thisDuration: Con TURNING = 5 y BASE_SEGMENT_MS = 300, tiempo total = 3600ms, por segmento = 720ms — limitado a 600ms. Con TURNING = 10 → 360ms por segmento. El tope evita que algún segmento de arco corra demasiado tiempo sin chequeo de sensores.
〰️
Ejemplo de turnRatio: TURNING = 2 → 0.24 (arco suave). TURNING = 10 → 0.40 (cerrado). El tope de la versión anterior desapareció — en cambio, roamStep sujeta el ratio entre 0.10 y 0.45 con variación aleatoria.
⬇️
Decaimiento de Endurance: Misma forma que antes — la potencia disminuye según la estadística ENDURANCE y simulatedMatchTime, con un piso del 20% para que el robot siempre pueda arrastrarse. El programa de partido usa matemática idéntica, así que probar aquí predice el comportamiento al final del partido.
🐢
Rastreo de baja potencia (nuevo): Si currentPower cae a la velocidad mínima de rueda, lowPowerActive = true. El resto del programa lee esta bandera para usar un umbral de sensor más suave, retroceso más corto, giro de recuperación más cerrado, y la tira RGB muestra rojo en lugar de azul/verde.
function driveAndWatch — Conducir + Monitorear Sensores Simultáneamente
Cargando bloques desde MakeCode…
function driveAndWatch(ms: number, lSpeed: number, rSpeed: number) {
    if (!running) { return }
    let watchStart = input.runningTime()
    let lastBorderPoll = 0
    let lastMotorRefresh = input.runningTime()

    while (input.runningTime() - watchStart < ms) {
        if (!running) { return }

        if (input.runningTime() - lastBorderPoll >= BORDER_POLL_MS) {
            lastBorderPoll = input.runningTime()
            readTrackbit()

            let hardSide = borderSideFrom(BORDER_THRESHOLD)
            if (hardSide > 0) {
                borderSide = hardSide
                CutebotPro.pwmCruiseControl(-60, -60)
                basic.pause(70)
                motorStop()
                borderHandledInSeg = true
                handleBorder()
                return
            }

            let softThresh = Math.round(BORDER_THRESHOLD * (lowPowerActive ? 0.65 : 0.75))
            let softSide = borderSideFrom(softThresh)
            if (softSide > 0) {
                softEvade(softSide)
                return
            }
        }

        // Motor keep-alive cada 200ms
        if (input.runningTime() - lastMotorRefresh > 200) {
            lastMotorRefresh = input.runningTime()
            CutebotPro.pwmCruiseControl(lSpeed, rSpeed)
        }

        basic.pause(5)
    }
}
          

El nuevo driveAndWatch hace tres trabajos a la vez: sondea los sensores del borde cada 12ms, vuelve a enviar el comando del motor cada 200ms para que el controlador no se trabe, y verifica dos umbrales de sensor separados — uno duro para un impacto real con el borde, y uno suave para advertencia temprana.

1
Tres temporizadores — watchStart rastrea cuánto tiempo ha estado corriendo el segmento, lastBorderPoll limita la tasa de lecturas de sensores, y lastMotorRefresh limita la tasa del comando keep-alive del motor.
2
Umbral duro — si algún sensor lee por encima de BORDER_THRESHOLD (130), el robot está sobre blanco. Frena a −60 PWM por 70ms, parada total, establece borderHandledInSeg = true, y llama a handleBorder para la secuencia de recuperación completa. Luego sale del segmento.
3
Umbral suave (nuevo) — si no hay impacto duro, verifica un umbral más suave al 75% del valor duro (o 65% cuando hay baja potencia). Esto detecta el borde de la zona blanca antes de que el robot esté completamente sobre ella, y activa softEvade — una recuperación más ligera que el handleBorder completo.
4
Keep-alive del motor (nuevo) — cada 200ms, reenvía el mismo comando de velocidad de rueda. Esto es una solución alternativa para una peculiaridad del controlador del Cutebot Pro donde los motores pueden dejar de responder a comandos largos sin interrupción. Sin esto, el robot dejaría de conducir a mitad de segmentos largos.
💡
Dos umbrales, dos recuperaciones. Impacto duro (ya sobre blanco) → handleBorder completo con retroceso, giro multi-intento, y reverificación. Impacto suave (a punto de estar sobre blanco) → softEvade rápido con retroceso más corto y un solo giro. Juntos, el robot rara vez se queda atascado en el borde.
function checkBorder — Leyendo los Sensores
Cargando bloques desde MakeCode…
function readTrackbit() {
    CutebotPro.trackbitStateValue()
    tb1 = CutebotPro.trackbitgetGray(TrackbitChannel.One)
    tb2 = CutebotPro.trackbitgetGray(TrackbitChannel.Two)
    tb3 = CutebotPro.trackbitgetGray(TrackbitChannel.Three)
    tb4 = CutebotPro.trackbitgetGray(TrackbitChannel.Four)
}

// 1 = frontal izq  2 = frontal der  3 = centro  4 = ambos externos (directo)  0 = despejado
function borderSideFrom(threshold: number): number {
    if (tb1 > threshold && tb4 > threshold) { return 4 }
    if (tb1 > threshold) { return 1 }
    if (tb4 > threshold) { return 2 }
    if (tb2 > threshold || tb3 > threshold) { return 3 }
    return 0
}

function checkBorder(): number {
    readTrackbit()
    let side = borderSideFrom(BORDER_THRESHOLD)
    if (side > 0) { borderSide = side }
    return side
}
          

La detección del borde ahora se divide en tres ayudantes. readTrackbit lee los cuatro sensores en variables compartidas. borderSideFrom clasifica qué lado del robot está sobre blanco, dado cualquier umbral. checkBorder usa el umbral duro y es lo que las otras funciones llaman.

1
readTrackbit — llama a trackbitStateValue una vez para refrescar, luego lee cada sensor (1–4) en tb1, tb2, tb3, tb4. Otras funciones leen esas variables en lugar de volver a llamar a la API del sensor.
2
borderSideFrom (nuevo — toma un umbral) — agrega un caso 4 = directo para cuando ambos sensores externos se activan. Esto es crítico porque un impacto directo necesita una recuperación mucho más dura (giro más grande) que un impacto de un solo lado.
3
checkBorder — usa el BORDER_THRESHOLD duro y almacena el resultado en el borderSide global para que handleBorder sepa hacia dónde girar.
↑ Frente del robot
tb1izq
tb2centro
tb3centro
tb4der
CUERPO DEL ROBOT
↓ Parte trasera del robot
solo tb1 → 1 (izquierda)
solo tb4 → 2 (derecha)
tb2 o tb3 → 3 (centro / directo)
tb1 + tb4 → 4 (directo total, nuevo)
Todo despejado → 0
function handleBorder — Recuperarse del Borde
Cargando bloques desde MakeCode…
function handleBorder() {
    if (!running) { return }
    hardStop()
    if (!running) { return }

    speedExtra = Math.max(SPEED - 4, 0)
    backupPower = lowPowerActive ? -25 : (0 - 40 - speedExtra * 5)
    backupTime = lowPowerActive ? 130 : (250 + speedExtra * 20)

    let attempts = 0
    while (attempts < 3) {
        attempts += 1
        let side = borderSide
        borderSide = 0

        CutebotPro.pwmCruiseControl(backupPower, backupPower)
        basic.pause(backupTime)
        CutebotPro.stopImmediately(CutebotProMotors.ALL)
        basic.pause(80)
        if (!running) { return }

        let angle: number
        if (side == 1 || side == 2) {
            angle = lowPowerActive ? 150 : 115
        } else {
            angle = side == 4 ? 150 + Math.randomRange(0, 20) : 130
            if (lowPowerActive) { angle = Math.min(angle + 30, 180) }
        }
        turnAwayFrom(side, angle)
        if (!running) { return }

        CutebotPro.stopImmediately(CutebotProMotors.ALL)
        basic.pause(40)
        CutebotPro.pwmCruiseControl(currentPower, currentPower)
        basic.pause(300)
        CutebotPro.stopImmediately(CutebotProMotors.ALL)
        basic.pause(40)

        if (checkBorder() == 0) { break }
    }
    sameDirectionCount = 0
}
          

La recuperación ahora es un bucle de 3 intentos. Cada intento: retroceder, girar y alejarse, conducir hacia adelante brevemente, luego volver a verificar el borde. Si el robot sigue sobre blanco, el bucle intenta de nuevo — hasta 3 veces. La distancia de retroceso, el ángulo de giro y la lógica de dirección todos escalan con SPEED y el estado de baja potencia.

1
Parada dura — hardStop invierte a −80 PWM por un tiempo de frenado que crece con SPEED. Un robot rápido tarda más en detenerse, así que frena más fuerte.
2
Ajuste de retroceso — speedExtra = SPEED − 4. Robots rápidos retroceden más con más potencia. Robots de baja potencia retroceden solo 130ms a −25 PWM (sin desperdicio de energía cuando el robot está cansado).
3
Ángulo de giro por lado — impacto de un solo lado (1 o 2): 115°. Directo (3 o 4): 130–150° con un empujón aleatorio. Baja potencia: +30° más amplio para que el robot no se quede atascado pivoteando hacia la pared.
4
turnAwayFrom (nuevo ayudante) — pivota en el lugar. lado 1 → derecha, lado 2 → izquierda, directo → alterna según el último giro (para que impactos directos consecutivos no oscilen).
5
Conducir adelante, luego reverificar — 300ms hacia adelante a currentPower para alejarse del borde, luego llama a checkBorder de nuevo. Si está despejado, break del bucle. Si sigue sobre blanco, comienza el siguiente intento.
6
Reiniciar contador de segmento — sameDirectionCount = 0 para que el siguiente roamStep elija una nueva racha de dirección.
🔁
3 intentos, no 1. Una sola recuperación a veces falla — retrocediendo a otro borde blanco, o girando hacia otra esquina. Hacer un bucle de tres veces es la diferencia entre "el robot se quedó atascado en la esquina" y "el robot escapó en 2 segundos."
function roamStep — Un Segmento de Arco (el modelo de movimiento)
Cargando bloques desde MakeCode…
function roamStep() {
    if (!running) { return }

    // Cambia dirección cada 1–2 segmentos (aleatorio)
    if (sameDirectionCount >= maxSameDirection) {
        lastTurnWasLeft = !lastTurnWasLeft
        sameDirectionCount = 0
        maxSameDirection = Math.randomRange(1, 2)
    } else {
        sameDirectionCount += 1
    }

    let dynamicRatio = Math.max(0.10, Math.min(0.45,
        turnRatio + Math.randomRange(-8, 8) / 100))

    let speedDurationBonus = SPEED * 12
    let dynamicDuration = lowPowerActive
        ? Math.max(Math.min(thisDuration / 2 + Math.randomRange(-50, 50), 300), 150)
        : Math.max(Math.min(thisDuration + speedDurationBonus + Math.randomRange(-50, 50), 550), 150)

    let lSpeed: number
    let rSpeed: number
    if (lastTurnWasLeft) {
        lSpeed = Math.round(currentPower * (1 - dynamicRatio))
        rSpeed = Math.round(currentPower * (1 + dynamicRatio))
        strip.showColor(lowPowerActive ? neopixel.colors(NeoPixelColors.Red)
            : neopixel.colors(NeoPixelColors.Blue))
    } else {
        lSpeed = Math.round(currentPower * (1 + dynamicRatio))
        rSpeed = Math.round(currentPower * (1 - dynamicRatio))
        strip.showColor(lowPowerActive ? neopixel.colors(NeoPixelColors.Red)
            : neopixel.rgb(0, 255, 0))
    }

    lSpeed = Math.max(lSpeed, minWheelSpeed)
    rSpeed = Math.max(rSpeed, minWheelSpeed)

    borderHandledInSeg = false
    CutebotPro.pwmCruiseControl(lSpeed, rSpeed)
    driveAndWatch(dynamicDuration, lSpeed, rSpeed)

    if (!borderHandledInSeg) {
        CutebotPro.pwmCruiseControl(-40, -40)
        basic.pause(40)
    }
    motorStop()

    if (running) {
        basic.pause(40)
        if (checkBorder() > 0) { handleBorder() }
    }
}
          

Aquí es donde sucede el movimiento real del robot. Cada llamada a roamStep conduce un segmento de arco: elegir una dirección (tal vez cambiar), elegir velocidades para las ruedas izquierda y derecha, conducir vigilando el borde, frenar y verificar el borde una vez más. El programa de partido usa la misma función — así que los patrones de arco que observas aquí son los patrones que el robot dibuja en partidos reales.

1
Cambiar dirección cada 1–2 segmentos — rastrea cuántos segmentos han usado la misma dirección (sameDirectionCount). Cuando llega a maxSameDirection, cambia. Por esto el robot zigzaguea en lugar de hacer bucles limpios en una dirección.
2
Agregar variación a turnRatio — dynamicRatio = turnRatio + random(−0.08, 0.08), sujeto entre 0.10 y 0.45. Dos segmentos con el mismo TURNING nunca trazan exactamente el mismo arco.
3
SPEED estira los segmentos — speedDurationBonus = SPEED × 12ms se agrega a la duración de cada segmento. Robots de SPEED alto cubren más terreno por segmento antes de cambiar. El estado de baja potencia divide la duración en lugar — robots cansados dan pasos más cortos y suaves.
4
Matemática de velocidad de rueda — currentPower × (1 − ratio) para la rueda interna, currentPower × (1 + ratio) para la externa. La rueda interna desacelera, la externa va más rápido que currentPower. Cada rueda con piso en minWheelSpeed para que la rueda interna nunca se estanque.
5
Retroalimentación RGB — giros a la izquierda muestran azul, giros a la derecha muestran verde, y el estado de baja potencia muestra rojo sin importar la dirección. La tira es una lectura visual del estado interno del robot.
6
Conducir, frenar, reverificar — llama a driveAndWatch con las velocidades de rueda elegidas para la duración elegida. Si volvió sin un impacto de borde (!borderHandledInSeg), hace un frenado activo firme a −40 PWM. Luego verifica el borde una vez más — a veces el robot rueda hacia una línea durante el frenado.
Ayudantes pequeños — motorStop, hardStop, softEvade, turnAwayFrom
Cargando bloques desde MakeCode…
function motorStop() {
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
    basic.pause(10)
    CutebotPro.pwmCruiseControl(0, 0)
    basic.pause(10)
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
}

function hardStop() {
    let brakeTime = 160 + Math.max(SPEED - 5, 0) * 35
    CutebotPro.pwmCruiseControl(-80, -80)
    basic.pause(brakeTime)
    CutebotPro.stopImmediately(CutebotProMotors.ALL)
}

function turnAwayFrom(side: number, angle: number) {
    if (side == 1) {
        CutebotPro.trolleySteering(CutebotProTurn.RightInPlace, angle)
        lastTurnWasLeft = false
    } else if (side == 2) {
        CutebotPro.trolleySteering(CutebotProTurn.LeftInPlace, angle)
        lastTurnWasLeft = true
    } else if (lastTurnWasLeft) {
        CutebotPro.trolleySteering(CutebotProTurn.RightInPlace, angle)
        lastTurnWasLeft = false
    } else {
        CutebotPro.trolleySteering(CutebotProTurn.LeftInPlace, angle)
        lastTurnWasLeft = true
    }
}

function softEvade(side: number) {
    motorStop()
    basic.pause(20)
    let backMs = lowPowerActive ? 130 : 200
    let backPwr = lowPowerActive ? -25 : -30
    CutebotPro.pwmCruiseControl(backPwr, backPwr)
    basic.pause(backMs)
    motorStop()
    basic.pause(40)
    turnAwayFrom(side, lowPowerActive ? 130 : 90)
    motorStop()
    borderHandledInSeg = true
    borderSide = 0
}
          

Cuatro funciones cortas que las funciones más grandes llaman. Cada una hace un trabajo limpiamente para que la lógica de recuperación se mantenga legible.

🛑
motorStop — un corte limpio de motor. Tres comandos con pausas de 10ms, porque el controlador a veces ignora un solo comando de parada cuando está en medio de PWM. Usado por todas las demás funciones de recuperación.
🚨
hardStop — inversión a −80 PWM por un tiempo de frenado que crece con SPEED. Un robot a SPEED 8 tarda más en detenerse que uno a SPEED 4, así que el tiempo de frenado debe compensar o el robot se pasa hacia blanco.
↪️
turnAwayFrom — pivota en el lugar por el ángulo dado. Lado 1 (impacto izq) → giro derecha. Lado 2 (impacto der) → giro izquierda. Lado 3 o 4 (directo) → alterna según el último giro, para que impactos directos consecutivos no oscilen.
🪶
softEvade — la recuperación más ligera para el umbral suave (de advertencia). Retroceso corto (130–200ms) y un solo giro (90–130°), sin bucle de reverificación. Usado cuando el robot se acerca al blanco pero no está sobre él — rápido y fuera del camino.

¿Qué significa cada variable?

Dorado = tú lo configuras. Cian = calculado automáticamente. Púrpura = constante fija.

SPEED Tú configuras
Estadística de velocidad de tu personajePor defecto 6. Establece la potencia base del motor. También afecta la duración del segmento (más larga en SPEED alto) y el tiempo de frenado de recuperación (más duro en SPEED alto).
ENDURANCE Tú configuras
Estadística de resistencia de tu personajePor defecto 4. Controla cuánto disminuye la potencia del motor durante simulatedMatchTime. ENDURANCE bajo = ralentización notable a 180s.
TURNING Tú configuras
Estadística de giro de tu personajePor defecto 3. El Desafío de Cobertura pide T = 2, 5 y 10. Maneja tanto la nitidez del arco (vía turnRatio) como la longitud del segmento (vía thisDuration).
motorPower Auto
Potencia fresca del motor antes del decaimientoCalculado como SPEED × 100 ÷ 20. El punto de partida para los cálculos de potencia. El decaimiento de endurance se aplica a esto para producir currentPower.
currentPower Auto
Potencia real del motor enviada a las ruedas (después del decaimiento)motorPower × enduranceFactor, con piso en minWheelSpeed. Esto es lo que las ruedas realmente hacen.
turnRatio Auto
Diferencia de velocidad de ruedas que crea el arco0.2 + TURNING × 0.02, sin tope duro aquí — roamStep lo sujeta a 0.10–0.45 con variación aleatoria.
thisDuration Auto
Duración base del segmento en mstotalArcTime ÷ TURNING, limitado a 600ms. roamStep agrega bonus de SPEED y variación aleatoria antes de conducir.
totalArcTime Auto
Presupuesto total de tiempo para un ciclo de arcoSiempre BASE_SEGMENT_MS × 12 = 3.600ms en esta versión. Dividido entre segmentos para obtener thisDuration.
minWheelSpeed Auto
Piso para cualquier rueda individualmax(20, motorPower × 0.25). Cada rueda está sujeta al menos a esto — evita que la rueda interna de un arco se estanque completamente.
enduranceFactor Auto
Multiplicador de decaimiento (0.2–1.0)endurancePercent ÷ 100, nunca por debajo de 0.20. Multiplicado con motorPower para obtener currentPower.
lowPowerActive Auto
Bandera de robot cansadoVerdadero cuando currentPower ≤ minWheelSpeed. Activa umbrales más suaves, retrocesos más cortos, giros de recuperación más amplios, y una tira RGB roja en lugar de azul/verde.
simulatedMatchTime Auto
Reloj de partido falso para pruebas de enduranceSe configura presionando el Botón B. 0 → 60 → 120 → 180. Maneja el cálculo de decaimiento en computeStats.
borderSide Auto
Qué lado del robot golpeó el borde por última vez1 = izquierda, 2 = derecha, 3 = centro, 4 = ambos externos (directo). Maneja la dirección de giro en handleBorder y turnAwayFrom.
lastTurnWasLeft Auto
Qué dirección fue el último arcoCambia cada 1–2 segmentos para hacer que el robot zigzaguee. También actualizado por handleBorder para que la recuperación se reanude mirando la dirección correcta.
sameDirectionCount Auto
Cuántos segmentos el robot ha usado la misma direcciónCuando llega a maxSameDirection (1 o 2, aleatorio), cambia. Por esto el robot zigzaguea en lugar de hacer bucles limpios.
TEST_DURATION_MS Constante
Cuánto dura cada ejecución de pruebaPor defecto 15000 (15 segundos). Coincide con el Desafío de Cobertura — tiempo suficiente para ver desarrollarse el patrón de arco y recuperarse de al menos un impacto de borde.
BASE_SEGMENT_MS Constante
Longitud base del segmento antes de dividir entre TURNINGPor defecto 300. Multiplicado por 12 para obtener totalArcTime. Cámbialo para hacer todos los segmentos más cortos o más largos.
BORDER_THRESHOLD Constante
Lectura del sensor para detección del bordePor defecto 130. Configúralo al valor que use tu programa de partido — deben coincidir. El umbral suave es automáticamente 75% de esto (o 65% cuando baja potencia).
BORDER_POLL_MS Constante
Intervalo de sondeo del sensor dentro de driveAndWatchPor defecto 12. Aproximadamente 80 lecturas por segundo — suficientemente rápido para reaccionar antes de que el robot pase un borde blanco.

Consejos para usar este programa

01

Edita tus estadísticas, vuelve a flashear

Los valores predeterminados son SPEED = 6, ENDURANCE = 4, TURNING = 3. Los predeterminados funcionan — cambia a las estadísticas de tu personaje y vuelve a flashear para comparar. Ya no hay nada "que llenar desde cero".

02

Ejecuta la secuencia del Desafío de Cobertura

Vuelve a flashear con TURNING = 2, luego 5, luego 10. Observa el patrón en el suelo que traza cada uno. El Desafío de Cobertura del Día 3 pide exactamente esto — TURNING alto = segmentos más cortos y cerrados (se queda local). TURNING bajo = segmentos más largos y suaves (cubre más).

03

Iguala el BORDER_THRESHOLD

Configura BORDER_THRESHOLD al valor que tu programa de partido calibra. Si no coinciden, el robot reaccionará al borde blanco de manera diferente en pruebas que en partidos.

04

Usa el Botón B antes que A

Presiona B para avanzar el tiempo simulado del partido, observa cómo cae el valor "P", luego presiona A. Comparar TURNING = 5 a 0s vs. 180s muestra cómo los arcos de un robot cansado se aplanan — y puedes predecirlo desde el número P antes de ejecutar.

05

Observa la tira RGB

Azul = arqueándose a la izquierda a potencia completa. Verde = arqueándose a la derecha a potencia completa. Rojo = el robot ha caído al estado de baja potencia — recuperación más suave, segmentos más cortos, giros más amplios. Si ves rojo temprano en una prueba de 15 segundos, tu asignación de ENDURANCE puede estar demasiado baja.

06

Mantén A+B al alcance

A+B es la parada de emergencia. Mantente cerca y presiona ambos botones juntos en el momento en que el robot vaya hacia donde no debería. La X en la pantalla LED confirma que los motores están apagados.