A web-based 2D robot programming simulator that lets users control a robot using Python for learning, experimentation, and education.
- 2D robot simulation with differential drive kinematics
- Integrated tabbed interface for switching between the code editor and robot settings
- Sensor APIs:
analogRead(index)for light sensorsgetUltrasonic(index)/ultrasonic(index)for ultrasonic sensorsgetCompass()/compass()for compass readings
- Actuator and object support:
grab(index)/release(index)for grips- Interactive canvas objects that can be dragged during runtime
- Dynamic modular sensor architecture where each sensor is a standalone package
- In-browser Python execution via Skulpt
- Automatic loop delay injection to reduce browser freezing from tight
whileloops - Configurable robot and sensor settings such as position, angle, color, and interaction options
- Open
index.htmlin your browser. - Switch to the Robot Settings tab.
- Add or configure Wheels, Ultrasonic sensors, Light sensors, Grips, Compass, and other supported modules.
- Return to the Code tab and write Python code using the simulator API.
- Click Run in the toolbar to start the simulation.
- Install Node.js 18+.
- From the project root, run:
npm test| Command | Description |
|---|---|
motor(left, right) |
Control motors with speed from -100 to 100. Example: motor(50, 50) |
delay(ms) |
Pause execution for ms milliseconds |
analogRead(index) |
Read value from a light sensor using type-relative indexing |
getUltrasonic(index) |
Read distance from an ultrasonic sensor |
ultrasonic(index) |
Alias for getUltrasonic(index) |
getCompass() |
Read the robot angle if a compass is installed |
compass() |
Alias for getCompass() |
SW(index) |
Check whether SW1, SW2, or SW3 is pressed |
waitSW(index) |
Pause execution until the selected switch is pressed |
grab(index) |
Activate a grip |
release(index) |
Release a grip |
spawn_object(color) |
Spawn an object with the given color |
getSensorCount() |
Return the total number of attached sensors |
print(message) |
Print text to the debug console |
- Type-relative indexing:
analogRead(0)is always the first light sensor, andgetUltrasonic(0)is always the first ultrasonic sensor. - Infinite loops: You can safely use
while True:because the system injectsdelay(1)into loop execution paths. - Runtime interaction: Canvas objects can still be dragged while code is running.
- Wheel management: The robot starts with a protected front wheel and can be extended with additional wheels.
- Dynamic storage: Standard sensors and actuators are stored in separate arrays based on each sensor's configuration.
The project includes a Node-based automation test suite for the core execution flow and the sensor plugin system.
npm testnpm run test:e2enpm run test:e2e:headednpm run test:e2e:uinpm run test:all- Core execution behavior in
src/core/executor.js - Shared sensor contract checks for every sensor under
src/sensors/ - Optional sensor-specific behavior tests when a sensor provides its own
*.test.js - Browser end-to-end coverage for app boot, run/stop/reset, sensor APIs, and settings flows
The sensor test system is designed to support future sensors without forcing every sensor to have a dedicated test file.
tests/sensors.test.jsautomatically discovers all sensor folders insrc/sensors/- Each sensor is checked for the expected module files:
config.jsonindex.jslogic.jsexecutor.jsrender.html
- Shared contract tests validate that sensor plugins can be imported and used safely
- If
config.jsondeclares Python APIs, the shared tests verify that those APIs are registered - If a sensor folder contains one or more
*.test.jsfiles, those tests are loaded automatically - If a sensor does not contain its own
.test.js, it still receives baseline coverage from the shared contract tests - Browser-level E2E tests live under
tests/e2e/and validate the integrated app in Playwright
When adding a new sensor in src/sensors/<name>/:
- Add the sensor files:
config.json,index.js,logic.js,executor.js, andrender.html - Register the sensor name in the root
config.json - Run
npm test - Optionally add
src/sensors/<name>/<name>.test.jsfor sensor-specific behavior - Run
npm run test:e2eif the new sensor affects browser flows, rendering, or Python execution behavior
This keeps every sensor testable by default while allowing richer tests for more complex sensors.