""" NRC 2026 RoboMission Rookie — "Robot Rockstar" ========================================================================= FAST SOLUTION (DriveBase rebuild) — a clean, pro-grade framework. This version swaps the hand-tuned .dc() toolkit for PyBricks' built-in DriveBase + gyro. More to set up, but the most accurate and the least tuning once your wheel + axle numbers are in. Why it beats a hand-tuned .dc() script on the clock AND on reliability: 1. DriveBase + gyro Drive in real MILLIMETRES and DEGREES, kept straight/accurate by the hub's gyro. You measure the wheel + axle ONCE — no re-tuning every single move like the .dc() version needs. 2. Aggressive-but-repeatable motion profile Speed and acceleration are pushed up to the reliable limit in one place (SETTINGS). Faster legs, fewer wasted seconds. 3. One geographic sweep — no backtracking Route flows RIGHT (start) -> CENTRE (notes) -> BOTTOM (truck) -> LEFT (stage) and ends near the stage. 4. Score-first order The 120-point notes are collected BEFORE any time pressure. 5. Arm actions overlap driving, and big repositions use smooth curves (drive.curve) instead of stop-turn-go. Target time: ~1:20-1:35 of the 2:00 limit, leaving margin. >>> Everything marked # CAL must be measured on YOUR robot and the venue mat. The starting numbers below are scaled from the official mat photo. ========================================================================= """ from pybricks.hubs import PrimeHub from pybricks.pupdevices import Motor, ColorSensor from pybricks.parameters import Port, Direction, Stop from pybricks.robotics import DriveBase from pybricks.tools import wait, StopWatch # ===================== HARDWARE ===================== hub = PrimeHub() # Drive wheels. Flip a Direction if a wheel spins the wrong way. left_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE) right_motor = Motor(Port.E, Direction.CLOCKWISE) front = Motor(Port.F) # front arm — grabs / releases the notes back = Motor(Port.B) # back arm — pushes the instruments floor = ColorSensor(Port.C) # kept for optional line-up checks # ===================== SETTINGS (CALIBRATE HERE) ===================== WHEEL_DIAMETER = 56 # CAL: your drive-wheel diameter, mm AXLE_TRACK = 112 # CAL: distance between the two wheels, mm (centre-centre) drive = DriveBase(left_motor, right_motor, WHEEL_DIAMETER, AXLE_TRACK) drive.use_gyro(True) # gyro keeps every straight line and turn honest # Fast but repeatable. Turn these UP until the robot stops being consistent, # then back off ~15%. straight in mm/s & mm/s², turn in deg/s & deg/s². drive.settings( straight_speed=420, straight_acceleration=750, turn_rate=320, turn_acceleration=900, ) timer = StopWatch() # ===================== REUSABLE MOVES ===================== def fwd(mm): drive.straight(mm) # + forward, - back def spin(deg): drive.turn(deg) # + clockwise def arc(radius_mm, deg): drive.curve(radius_mm, deg) # smooth turn-while-driving def grab(): front.run_angle(900, 220, then=Stop.HOLD) # close front arm def drop(): front.run_angle(900, -220, then=Stop.HOLD) # open front arm def push(): back.run_angle(900, 220, then=Stop.HOLD) # extend back pusher def retract(): back.run_angle(900, -220, then=Stop.HOLD) # pull back pusher def arm_async(motor, speed, angle): """Start an arm move WITHOUT waiting, so it overlaps the next drive.""" motor.run_angle(speed, angle, wait=False) # ===================== MISSION 1: PLAY THE SONG (120 pts) ===================== # REALITY: 3 notes per trip (stack 2 in front + push 1 with back); the colour # zones are at the BOTTOM-LEFT, so each batch is CARRIED there. Six notes = two # collect->carry->drop trips (the coach's structure). DriveBase lets us drop the # straight speed while loaded so nothing topples, then go fast again. NOTE_STEP = 171 # measured off mat photo: avg note-to-note spacing, mm ZONE_GAP = 90 # CAL: small placing move between colour zones, mm CARRY_MM = 1280 # measured: staff centroid (1464,540) -> bottom-left zones (~250,950), mm def collect_three(): fwd(NOTE_STEP); grab() # note 1 -> stack (front) fwd(NOTE_STEP); grab() # note 2 -> stack (front) fwd(NOTE_STEP); push() # note 3 -> push (back) def deliver_three(): # RULE NOTE (organizer update, 5 Jun 2026): a note only scores while UPRIGHT, # and holding it at the end "can lift it slightly" and fail the upright check. # Fully RELEASE each note and BACK OFF — never end a run still gripping one. # "Completely in": seat each note inside ONE zone only. spin(-90) # CAL: face the bottom-left zones drive.settings(straight_speed=260) # slow down — carrying a load fwd(CARRY_MM) # carry the batch to the zones drive.settings(straight_speed=420) # back to full speed retract() # drop the pushed note, upright fwd(ZONE_GAP); drop() # release a stacked note, upright fwd(ZONE_GAP); drop() # release the other, upright fwd(-80) # back off so the arm clears the notes def play_the_song(): fwd(540); spin(-20) # CAL: start corner -> onto the staff collect_three(); deliver_three() # Trip 1: nearest three spin(180); fwd(CARRY_MM); spin(180) # fast empty return to the staff (~1280 mm) collect_three(); deliver_three() # Trip 2: far three # ===================== MISSION 2: INSTRUMENTS (45 pts) ===================== # From the last (yellow) note, drop to the truck at bottom-centre, capture the # three instruments on the back pusher and shove them into the backstage box. # No "upright" requirement here — a firm straight push scores. def prepare_instruments(): arc(180, 35) # CAL: curve down toward the truck fwd(430) # CAL: into the truck, behind the instruments push() # extend pusher behind all three spin(90) # CAL: face the backstage (stage, left side) fwd(360) # CAL: drive the instruments into the backstage box retract() # ===================== MISSION 3: MICROPHONE (20 pts) ===================== # The mic also starts in the truck; place it upright in its target. def place_microphone(): fwd(-120) # CAL: ease back off the backstage spin(-90) # CAL: turn to the mic target fwd(150) # CAL: nose the mic into its circle arm_async(front, 600, 120) # tip the mic upright while we settle wait(150) # ===================== MISSION 4: CABLES (30 pts) ===================== # Last, because the stage (amplifier + speakers) is at the far left and these # are the cheapest points. Lay both cables into the grey areas, upright. def connect_cables(): spin(180) # CAL: face along the stage front fwd(300) # CAL: to the upper cable area drop() # release cable 1 fwd(-560) # CAL: down to the lower cable area grab(); drop() # seat + release cable 2 # ===================== MAIN ===================== def main(): timer.reset() hub.imu.reset_heading(0) play_the_song() # 120 — do the big points first prepare_instruments() # 45 place_microphone() # 20 connect_cables() # 30 # Bonus (40): we never touch the clef / amp / speakers, so it's free. drive.stop() # Optional: flash how much time we had to spare. hub.display.number(int((120000 - timer.time()) / 1000)) main()