Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Brett Fiedler
Published © CC BY

Micro:bit and Paper Playground: Tangible-Virtual Interfaces

Craft physical-virtual, interactive experiences using code, paper, and web components with Paper Playground & BBC micro:bit!

BeginnerProtip1 hour162
Micro:bit and Paper Playground: Tangible-Virtual Interfaces

Things used in this project

Hardware components

BBC micro:bit board
BBC micro:bit board
×1
(optional) BBC micro:bit battery pack
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1
Alligator Clips
Alligator Clips
×3
LED (generic)
LED (generic)
×2
Webcam (generic, at least 720p+ resolution, 1080p recommended)
×1
(optional) structural support to suspend webcam
×1

Software apps and online services

Paper Playground
Microsoft Makecode for BBC micro:bit

Story

Read more

Schematics

microbit-playper-bridge-v3.hex

Upload to BBC micro:bit v2 directly or through the Makecode website.

Small Paper Programs, 1-20

Small paper programs ready for printing! Program numbers 1-20.

Quarter Sheet Paper Programs, 1-20

Quarter sheet paper programs ready for printing! Program numbers 1-20.

Half Sheet Paper Programs, 1-20

Half sheet paper programs ready for printing! Program numbers 1-20.

Letter-sized Paper Programs, 1-20

Letter-sized paper programs ready for printing! Program numbers 1-20.

Code

microbit-ble-demos.json

JSON
Upload to Paper Playground (Creator window) for Example 2.
{"programs":[{"number":5,"title":"Hopping Frog","keywords":"microbit, button","description":"When triggered, buttonPressed boolean triggers an image change to animate a hopping frog (pair with BLE Button program).","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":331,"y":326.4286175999101},"modelContainer":{"namedBooleanProperties":[{"name":"buttonPressed","defaultValue":false,"propertyType":"BooleanProperty"}],"namedVector2Properties":[],"namedNumberProperties":[],"namedEnumerationProperties":[],"namedDerivedProperties":[],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[],"numberPropertyControllers":[],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"speechViews":[],"textViews":[],"shapeViews":[],"backgroundViews":[{"name":"blueBackground","modelComponentNames":[],"referenceComponentNames":[],"controlFunctionString":"","lazyLink":false,"fillColor":"#AFEEEE"}],"imageViews":[{"name":"hoppingImage","modelComponentNames":["buttonPressed"],"referenceComponentNames":[],"controlFunctionString":"// hopping when button is pressed\r\nsetVisible( buttonPressed );\r\n\r\nsetCenterX( 0.5 );\r\nsetCenterY( 0.5 );","lazyLink":false,"defaultViewOptions":{"centerX":0.5,"centerY":0.5,"scale":1,"rotation":0,"opacity":1,"visible":true,"viewUnits":"model"},"imageFileName":"hopping-frog.gif"},{"name":"idleImage","modelComponentNames":["buttonPressed"],"referenceComponentNames":[],"controlFunctionString":"// idle when not pressed - boolean is false\r\nsetVisible( !buttonPressed );\r\n\r\nsetCenterX( 0.5 );\r\nsetCenterY( 0.5 );","lazyLink":false,"defaultViewOptions":{"centerX":0.5,"centerY":0.5,"scale":1,"rotation":0,"opacity":1,"visible":true,"viewUnits":"model"},"imageFileName":"idle-frog.gif"}]},"listenerContainer":{"linkListeners":[],"animationListeners":[],"bluetoothListeners":[]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}},{"number":4,"title":"BLE Button","keywords":"microbit, button, controller","description":"Example for reading and setting a model component value using a button press from a micro:bit.","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":331,"y":125.69665612297524},"modelContainer":{"namedBooleanProperties":[],"namedVector2Properties":[],"namedNumberProperties":[],"namedEnumerationProperties":[],"namedDerivedProperties":[],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[],"numberPropertyControllers":[],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"speechViews":[],"textViews":[],"shapeViews":[],"backgroundViews":[],"imageViews":[]},"listenerContainer":{"linkListeners":[],"animationListeners":[],"bluetoothListeners":[{"name":"bleButtonController","controlledPropertyNames":["buttonPressed"],"controlFunctionString":"\r\n// The button is pressed if the deviceValue (8bit unsigned int)\r\n// is greater than 0.\r\nsetButtonPressed( deviceValue.getUint8( 0 ) > 0 );","dependencyNames":[],"referenceComponentNames":[],"writeToCharacteristic":false,"serviceId":"e95d9882-251d-470a-a062-fa1922dfa9a8","characteristicId":"e95dda91-251d-470a-a062-fa1922dfa9a8"}]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}},{"number":2,"title":"LED Text Emoji by Rotation","keywords":"","description":"A simple program to send a happy or sad face as LED Text to the micro:bit by rotating the paper program.","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":21.863280662047472,"y":86.93885923569876},"modelContainer":{"namedBooleanProperties":[{"name":"paperRotated","defaultValue":false,"propertyType":"BooleanProperty"}],"namedVector2Properties":[],"namedNumberProperties":[],"namedEnumerationProperties":[],"namedDerivedProperties":[],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[{"name":"rotationController","controlledComponentName":"paperRotated","controlType":"ROTATION","controlTypeFamily":"","whiskerConfiguration":{"topLength":0.2,"rightLength":0.2,"bottomLength":0.2,"leftLength":0.2,"otherPaperNumber":null},"markerColor":""}],"numberPropertyControllers":[],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"speechViews":[],"textViews":[],"shapeViews":[],"backgroundViews":[],"imageViews":[]},"listenerContainer":{"linkListeners":[],"animationListeners":[],"bluetoothListeners":[{"name":"bleFaceController","controlledPropertyNames":[],"controlFunctionString":"// micro:bit LED Text expects a UTF8 string.\r\n// Create an encoder and encode. Do not use writeStringToCharacteristic()\r\n// as this will add delimiters to your string.\r\n\r\nconst encoder = new TextEncoder();\r\n\r\nconst faceString = paperRotated ? ':)' : ':(';\r\nwriteToCharacteristic( encoder.encode( faceString ) );\r\n\r\nconsole.log( `sent data: ${faceString}` );","dependencyNames":["paperRotated"],"referenceComponentNames":[],"writeToCharacteristic":true,"serviceId":"e95dd91d-251d-470a-a062-fa1922dfa9a8","characteristicId":"e95d93ee-251d-470a-a062-fa1922dfa9a8"}]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}},{"number":7,"title":"UART to IO Pins (LEDs) with Markers","keywords":"","description":"This program sends a string over UART RX to set p0 to up (red marker) or p1 to up (green marker), which will light up an LED in circuit with those pins.","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":138.38747850334147,"y":188.20450434275028},"modelContainer":{"namedBooleanProperties":[{"name":"led1p0UpBoolean","defaultValue":false,"propertyType":"BooleanProperty"},{"name":"led2p1UpBoolean","defaultValue":false,"propertyType":"BooleanProperty"}],"namedVector2Properties":[],"namedNumberProperties":[],"namedEnumerationProperties":[],"namedDerivedProperties":[],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[{"name":"receivedUARTString","propertyType":"StringProperty","defaultValue":""}]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[{"name":"p0RedMarkerController","controlledComponentName":"led1p0UpBoolean","controlType":"MARKER","controlTypeFamily":"","whiskerConfiguration":{"topLength":0.2,"rightLength":0.2,"bottomLength":0.2,"leftLength":0.2,"otherPaperNumber":null},"markerColor":"red"},{"name":"p1GreenMarkerController","controlledComponentName":"led2p1UpBoolean","controlType":"MARKER","controlTypeFamily":"","whiskerConfiguration":{"topLength":0.2,"rightLength":0.2,"bottomLength":0.2,"leftLength":0.2,"otherPaperNumber":null},"markerColor":"green"}],"numberPropertyControllers":[],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"speechViews":[],"textViews":[{"name":"receivedUARTText","modelComponentNames":["receivedUARTString"],"referenceComponentNames":[],"controlFunctionString":"setTop(0.5);\r\nsetLeft(0.5);\r\nsetFontSize(32);\r\nsetString( \"micro:bit confirms:\" + receivedUARTString );","lazyLink":false,"defaultViewOptions":{"centerX":0.5,"centerY":0.5,"scale":1,"rotation":0,"opacity":1,"visible":true,"viewUnits":"model"}}],"shapeViews":[],"backgroundViews":[],"imageViews":[]},"listenerContainer":{"linkListeners":[],"animationListeners":[],"bluetoothListeners":[{"name":"p0BLEWriter","controlledPropertyNames":[],"controlFunctionString":"let str = \"\";\r\n\r\nif (led1p0UpBoolean) {\r\n    str = \"p0U\";\r\n} else {\r\n    str = \"p0D\";\r\n}\r\n\r\nwriteStringToCharacteristic(str);\r\n\r\nconsole.log(`pin 0 being set to ${str}`);\r\n","dependencyNames":["led1p0UpBoolean"],"referenceComponentNames":[],"writeToCharacteristic":true,"serviceId":"6e400001-b5a3-f393-e0a9-e50e24dcca9e","characteristicId":"6e400003-b5a3-f393-e0a9-e50e24dcca9e"},{"name":"p1BLEWriter","controlledPropertyNames":[],"controlFunctionString":"let str = \"\";\r\n\r\nif (led2p1UpBoolean) {\r\n    str = \"p1U\";\r\n} else {\r\n    str = \"p1D\";\r\n}\r\n\r\nwriteStringToCharacteristic(str);\r\n\r\nconsole.log(`pin 1 being set to ${str}`);","dependencyNames":["led2p1UpBoolean"],"referenceComponentNames":[],"writeToCharacteristic":true,"serviceId":"6e400001-b5a3-f393-e0a9-e50e24dcca9e","characteristicId":"6e400003-b5a3-f393-e0a9-e50e24dcca9e"},{"name":"readMicrobitUARTStrings","controlledPropertyNames":["receivedUARTString"],"controlFunctionString":"setReceivedUARTString( deviceValueString );\r\n\r\nconsole.log( `received ${deviceValueString}` );","dependencyNames":[],"referenceComponentNames":[],"writeToCharacteristic":false,"serviceId":"6e400001-b5a3-f393-e0a9-e50e24dcca9e","characteristicId":"6e400002-b5a3-f393-e0a9-e50e24dcca9e"}]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}},{"number":9,"title":"Simple UART Example","keywords":"","description":"","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":180.07511944486933,"y":527.4271624275366},"modelContainer":{"namedBooleanProperties":[{"name":"happyBoolean","defaultValue":false,"propertyType":"BooleanProperty"}],"namedVector2Properties":[],"namedNumberProperties":[],"namedEnumerationProperties":[],"namedDerivedProperties":[],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[{"name":"happyBooleanController","controlledComponentName":"happyBoolean","controlType":"MARKER","controlTypeFamily":"","whiskerConfiguration":{"topLength":0.2,"rightLength":0.2,"bottomLength":0.2,"leftLength":0.2,"otherPaperNumber":null},"markerColor":""}],"numberPropertyControllers":[],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"speechViews":[],"textViews":[],"shapeViews":[],"backgroundViews":[],"imageViews":[]},"listenerContainer":{"linkListeners":[],"animationListeners":[],"bluetoothListeners":[{"name":"writeToMicrobit","controlledPropertyNames":[],"controlFunctionString":"let str = \"\";\r\n\r\nif (happyBoolean) {\r\n    str = \"happy\";\r\n} else {\r\n    str = \"sad\";\r\n}\r\n\r\nwriteStringToCharacteristic(str);\r\n\r\nconsole.log(`Sent ${str} to micro:bit.`);","dependencyNames":["happyBoolean"],"referenceComponentNames":[],"writeToCharacteristic":true,"serviceId":"6e400001-b5a3-f393-e0a9-e50e24dcca9e","characteristicId":"6e400003-b5a3-f393-e0a9-e50e24dcca9e"}]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}}]}

microbit-servo-demo.json

JSON
Upload to Paper Playground (Creator window) for Example 1.
{"programs":[{"number":7,"title":"Servo Control by Rotation","keywords":"rotation, servo, microbit, uart","description":"Sends rotation value mapped from 0 - 180 as UART string to be read by micro:bit and set servo pin in order to set servo position.","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":194.49221091091607,"y":285.21493020035035},"modelContainer":{"namedBooleanProperties":[],"namedVector2Properties":[],"namedNumberProperties":[{"name":"servoPositionNumber","propertyType":"NumberProperty","min":"-90","max":"270","defaultValue":"90"}],"namedEnumerationProperties":[],"namedDerivedProperties":[],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[{"name":"servoValueString","propertyType":"StringProperty","defaultValue":""}]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[],"numberPropertyControllers":[{"name":"setServoPosition","controlledComponentName":"servoPositionNumber","controlType":"ROTATION","controlTypeFamily":"PAPER_MOVEMENT","markerColor":"all","relationshipControlType":"LINEAR"}],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"descriptionViews":[],"textViews":[],"shapeViews":[],"backgroundViews":[],"imageViews":[]},"listenerContainer":{"linkListeners":[{"name":"numberToStringforUART","controlledPropertyNames":["servoValueString"],"controlFunctionString":"//Only send integers to the servo, easier on the makecode string to number parser to have an integer\r\nsetServoValueString(String(Math.round(servoPositionNumber)));","dependencyNames":["servoPositionNumber"],"referenceComponentNames":[]}],"animationListeners":[],"bluetoothListeners":[{"name":"sendServoPositionToMicroBitRX","controlledPropertyNames":[],"controlFunctionString":"writeToCharacteristic(servoValueString);\r\n\r\nconsole.log(`Sending ${servoValueString} to microbit`);","dependencyNames":["servoValueString"],"referenceComponentNames":[],"writeToCharacteristic":true,"serviceId":"6e400001-b5a3-f393-e0a9-e50e24dcca9e","characteristicId":"6e400003-b5a3-f393-e0a9-e50e24dcca9e"}]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}}]}

microbit-ratio-game.json

JSON
Upload to Paper Playground (Creator window) for Example 3.
{"programs":[{"number":9,"title":"Ratio Game Logic (required)","keywords":"microbit, button, uart","description":"Creates a simple game to guess the ratio between the jumping heights of the dog and fox. Reads and logs the duration of a micro:bit button A and B press. Duration is stored to guess the goal ratio between the left and right animal jumping heights after rotating the paper to \"begin\". Duration is received by button service, while a UART RX sends the result as a happy or sad face to the micro:bit LED matrix.","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":223.86789338066544,"y":167.56653478945907},"modelContainer":{"namedBooleanProperties":[{"name":"buttonAPressed","defaultValue":false,"propertyType":"BooleanProperty"},{"name":"buttonBPressed","defaultValue":false,"propertyType":"BooleanProperty"},{"name":"readyToLaunch","defaultValue":false,"propertyType":"BooleanProperty"},{"name":"readyForNext","defaultValue":false,"propertyType":"BooleanProperty"}],"namedVector2Properties":[{"name":"leftObjectPosition","propertyType":"Vector2Property","defaultX":"0.25","defaultY":"1"},{"name":"rightObjectPosition","propertyType":"Vector2Property","defaultX":"0.75","defaultY":"1"}],"namedNumberProperties":[{"name":"buttonADuration","propertyType":"NumberProperty","min":0,"max":"10000","defaultValue":"0"},{"name":"buttonBDuration","propertyType":"NumberProperty","min":0,"max":"10000","defaultValue":"0"},{"name":"buttonAPressTime","propertyType":"NumberProperty","min":0,"max":"9999999999999","defaultValue":"0"},{"name":"buttonBPressTime","propertyType":"NumberProperty","min":0,"max":"999999999999","defaultValue":"0"},{"name":"toleranceValue","propertyType":"NumberProperty","min":0,"max":"1","defaultValue":"0.1"}],"namedEnumerationProperties":[],"namedDerivedProperties":[{"name":"logger","propertyType":"DerivedProperty","dependencyNames":["buttonADuration","buttonBDuration"],"derivation":"console.log(`button A: ${buttonADuration}`);\r\nconsole.log(`button B: ${buttonBDuration}`);"},{"name":"buttonPressedLogger","propertyType":"DerivedProperty","dependencyNames":["buttonAPressed","buttonBPressed"],"derivation":"console.log(`button A pressed?: ${buttonAPressed}`);\r\nconsole.log(`button B pressed?: ${buttonBPressed}`);"},{"name":"goalRatio","propertyType":"DerivedProperty","dependencyNames":["numeratorValue","denominatorValue"],"derivation":"let goal = 0.5;\r\n\r\nif (numeratorValue && denominatorValue) {\r\n    goal = numeratorValue / denominatorValue;\r\n} else {\r\n    goal = 0.5;\r\n}\r\n\r\nconsole.log(`Goal Ratio set to ${goal}`);\r\n\r\nreturn goal;"},{"name":"isInGoalRatioDerived","propertyType":"DerivedProperty","dependencyNames":["buttonADuration","buttonBDuration","readyToLaunch","goalRatio","toleranceValue"],"derivation":"const ratio = buttonADuration / buttonBDuration;\r\nconst isWithinTolerance = Math.abs(ratio - goalRatio) <= toleranceValue;\r\nconsole.log(`Are we within tolerance? ${isWithinTolerance}`);\r\nreturn isWithinTolerance;"},{"name":"readyToLaunchLogger","propertyType":"DerivedProperty","dependencyNames":["readyToLaunch"],"derivation":"if (readyToLaunch) {\r\n    console.log(`Ready to Launch!`);\r\n} else {\r\n    console.log(`Waiting to begin...`);\r\n}"}],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[{"name":"setReadyToLaunch","controlledComponentName":"readyToLaunch","controlType":"MARKER","controlTypeFamily":"","whiskerConfiguration":{"topLength":0.2,"rightLength":0.2,"bottomLength":0.2,"leftLength":0.2,"otherPaperNumber":null},"markerColor":""}],"numberPropertyControllers":[],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"descriptionViews":[],"textViews":[],"shapeViews":[{"name":"buttonAIndicator","modelComponentNames":["buttonAPressed"],"referenceComponentNames":[],"controlFunctionString":"if (buttonAPressed) {\r\n    setVisible(true);\r\n} else {\r\n    setVisible(false);    \r\n}","lazyLink":false,"defaultViewOptions":{"centerX":"0.1","centerY":"0.1","scale":1,"rotation":0,"opacity":1,"visible":true,"viewUnits":"model"},"defaultShapeOptions":{"shapeType":"circle","fill":"red","stroke":"red","lineWidth":1,"rectWidth":0.5,"rectHeight":0.5,"circleRadius":0.05,"ellipseRadiusX":0.3,"ellipseRadiusY":0.15,"lineStartX":0,"lineStartY":0,"lineEndX":0.3,"lineEndY":0.3,"triangleBaseWidth":0.1,"triangleHeight":0.3,"polygonPoints":[[0,0],[0.1,0],[0.1,0.1],[0,0.1]]}},{"name":"buttonBIndicator","modelComponentNames":["buttonBPressed"],"referenceComponentNames":[],"controlFunctionString":"if (buttonBPressed) {\r\n    setVisible(true);\r\n} else {\r\n    setVisible(false);    \r\n}","lazyLink":false,"defaultViewOptions":{"centerX":"0.9","centerY":"0.1","scale":1,"rotation":0,"opacity":1,"visible":true,"viewUnits":"model"},"defaultShapeOptions":{"shapeType":"circle","fill":"green","stroke":"green","lineWidth":1,"rectWidth":0.5,"rectHeight":0.5,"circleRadius":0.05,"ellipseRadiusX":0.3,"ellipseRadiusY":0.15,"lineStartX":0,"lineStartY":0,"lineEndX":0.3,"lineEndY":0.3,"triangleBaseWidth":0.1,"triangleHeight":0.3,"polygonPoints":[[0,0],[0.1,0],[0.1,0.1],[0,0.1]]}}],"backgroundViews":[],"imageViews":[{"name":"leftObjectImage","modelComponentNames":["leftObjectPosition"],"referenceComponentNames":[],"controlFunctionString":"setCenterX( leftObjectPosition.x );\r\nsetCenterY( leftObjectPosition.y );\r\nsetScale( 0.5 );","lazyLink":false,"defaultViewOptions":{"centerX":0.5,"centerY":0.5,"scale":1,"rotation":0,"opacity":1,"visible":true,"viewUnits":"model"},"imageFileName":"brown-dog-leaping.png"},{"name":"rightObjectImage","modelComponentNames":["rightObjectPosition"],"referenceComponentNames":[],"controlFunctionString":"setCenterX( rightObjectPosition.x );\r\nsetCenterY( rightObjectPosition.y );\r\nsetScale( 0.5 );","lazyLink":false,"defaultViewOptions":{"centerX":0.5,"centerY":0.5,"scale":1,"rotation":0,"opacity":1,"visible":true,"viewUnits":"model"},"imageFileName":"brown-fox-leaping.png"},{"name":"backgroundImage","modelComponentNames":[],"referenceComponentNames":[],"controlFunctionString":"moveToBack();\r\nsetScale(0.75);\r\nsetCenterX(0.5);\r\nsetCenterY(0.5);","lazyLink":false,"defaultViewOptions":{"centerX":0.5,"centerY":0.5,"scale":1,"rotation":0,"opacity":1,"visible":true,"viewUnits":"model"},"imageFileName":"meadow-landscape.png"}]},"listenerContainer":{"linkListeners":[{"name":"setButtonAPressTime","controlledPropertyNames":["buttonAPressTime"],"controlFunctionString":"if ( buttonAPressed ) {\r\n    setButtonAPressTime(Date.now());\r\n} else {\r\n    setButtonAPressTime( 0 );    \r\n}","dependencyNames":["buttonAPressed"],"referenceComponentNames":[]},{"name":"setButtonBPressTime","controlledPropertyNames":["buttonBPressTime"],"controlFunctionString":"if ( buttonBPressed ) {\r\n    setButtonBPressTime(Date.now());\r\n} else {\r\n    setButtonBPressTime( 0 );    \r\n}","dependencyNames":["buttonBPressed"],"referenceComponentNames":[]}],"animationListeners":[{"name":"setButtonADuration","controlledPropertyNames":["buttonAPressed","buttonADuration","buttonAPressTime"],"controlFunctionString":"if (buttonAPressed) {\r\n    setButtonADuration(Date.now() - buttonAPressTime)\r\n}"},{"name":"setButtonBDuration","controlledPropertyNames":["buttonBPressed","buttonBDuration","buttonBPressTime"],"controlFunctionString":"if (buttonBPressed) {\r\n    setButtonBDuration(Date.now() - buttonBPressTime)\r\n}"},{"name":"animateObjectHeights","controlledPropertyNames":["readyToLaunch","readyForNext","leftObjectPosition","rightObjectPosition","buttonADuration","buttonBDuration"],"controlFunctionString":"if (readyToLaunch) {\r\n    // Calculate the target Y positions, normalizing to the screen's height.\r\n    // Assuming a maximum reasonable button press duration (e.g., 5 seconds),\r\n    // and inverting the height since 0 is the top and 1 is the bottom.\r\n    const maxDuration = 10000;  // Adjust maxDuration based on expected max button press time\r\n    const targetYLeft = 1 - Math.min(buttonADuration / maxDuration, 1);\r\n    const targetYRight = 1 - Math.min(buttonBDuration / maxDuration, 1);\r\n\r\n    // Gradually update positions using dt to create a smooth animation effect\r\n    const newYLeft = leftObjectPosition.y + (targetYLeft - leftObjectPosition.y) * dt;\r\n    const newYRight = rightObjectPosition.y + (targetYRight - rightObjectPosition.y) * dt;\r\n\r\n    setLeftObjectPosition(new phet.dot.Vector2(leftObjectPosition.x, newYLeft));\r\n    setRightObjectPosition(new phet.dot.Vector2(rightObjectPosition.x, newYRight));\r\n\r\n    // Check if both objects have nearly reached their target positions to stop the animation\r\n    if (Math.abs(newYLeft - targetYLeft) < 0.02 && Math.abs(newYRight - targetYRight) < 0.02) {\r\n        setReadyToLaunch(false); // Stop the animation when both targets are reached\r\n        setTimeout(() => {\r\n            console.log(\"2 seconds have passed.\");\r\n        }, 2000);  // 2000 milliseconds = 2 seconds\r\n        setLeftObjectPosition(new phet.dot.Vector2(0.25, 1));\r\n        setRightObjectPosition(new phet.dot.Vector2(0.75, 1));\r\n    }\r\n\r\n}"}],"bluetoothListeners":[{"name":"setButtonAPressed","controlledPropertyNames":["buttonAPressed"],"controlFunctionString":"if (deviceValue.getUint8(0) > 0) {\r\n    setButtonAPressed(true);\r\n} else {\r\n    setButtonAPressed(false);\r\n}","dependencyNames":[],"referenceComponentNames":[],"writeToCharacteristic":false,"serviceId":"e95d9882-251d-470a-a062-fa1922dfa9a8","characteristicId":"e95dda90-251d-470a-a062-fa1922dfa9a8"},{"name":"setButtonBPressed","controlledPropertyNames":["buttonBPressed"],"controlFunctionString":"if (deviceValue.getUint8(0) > 0) {\r\n    setButtonBPressed(true);\r\n} else {\r\n    setButtonBPressed(false);\r\n}","dependencyNames":[],"referenceComponentNames":[],"writeToCharacteristic":false,"serviceId":"e95d9882-251d-470a-a062-fa1922dfa9a8","characteristicId":"e95dda91-251d-470a-a062-fa1922dfa9a8"},{"name":"bleWriteSuccessOrFail","controlledPropertyNames":[],"controlFunctionString":"// Construct the command string based on LED state\r\nlet str = \"\";\r\n\r\nif (readyToLaunch) {\r\n    if (isInGoalRatioDerived) {\r\n        str = \"success\"; // Command for LED pin 0 Up\r\n    } else {\r\n        str = \"failure\"; // Command for LED pin 0 Down\r\n    }\r\n}\r\n\r\n// Send the command over BLE\r\nwriteStringToCharacteristic(str);\r\nconsole.log(`Command sent: ${str}`);\r\n","dependencyNames":["readyToLaunch","isInGoalRatioDerived"],"referenceComponentNames":[],"writeToCharacteristic":true,"serviceId":"6e400001-b5a3-f393-e0a9-e50e24dcca9e","characteristicId":"6e400003-b5a3-f393-e0a9-e50e24dcca9e"}]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}},{"number":7,"title":"Set Numerator with Markers","keywords":"marker","description":"Sets the numerator value by counting the number of markers on the program. If not present, the default ratio is 0.5.","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":407.5,"y":266.61477406305124},"modelContainer":{"namedBooleanProperties":[],"namedVector2Properties":[],"namedNumberProperties":[{"name":"numeratorValue","propertyType":"NumberProperty","min":0,"max":10,"defaultValue":"0"}],"namedEnumerationProperties":[],"namedDerivedProperties":[],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[],"numberPropertyControllers":[{"name":"setNumeratorValueMarker","controlledComponentName":"numeratorValue","controlType":"MARKER_COUNT","controlTypeFamily":"MARKERS","markerColor":"all","relationshipControlType":null}],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"descriptionViews":[],"textViews":[],"shapeViews":[],"backgroundViews":[],"imageViews":[]},"listenerContainer":{"linkListeners":[],"animationListeners":[],"bluetoothListeners":[]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}},{"number":8,"title":"Set Denominator with Markers","keywords":"markers","description":"Sets the denominator value by counting the number of markers on the program. If not present, the default ratio is 0.5.","expanded":true,"topWhiskerLength":0.2,"rightWhiskerLength":0.2,"bottomWhiskerLength":0.2,"leftWhiskerLength":0.2,"positionProperty":{"x":407.5,"y":486.96403923693947},"modelContainer":{"namedBooleanProperties":[],"namedVector2Properties":[],"namedNumberProperties":[{"name":"denominatorValue","propertyType":"NumberProperty","min":0,"max":10,"defaultValue":"0"}],"namedEnumerationProperties":[],"namedDerivedProperties":[],"namedBounds2Properties":[],"namedObservableArrays":[],"namedArrayItems":[],"namedArrayItemReferences":[],"namedStringProperties":[]},"controllerContainer":{"vector2PropertyControllers":[],"boundsPropertyControllers":[],"booleanPropertyControllers":[],"numberPropertyControllers":[{"name":"setDenominatorValueMarker","controlledComponentName":"denominatorValue","controlType":"MARKER_COUNT","controlTypeFamily":"MARKERS","markerColor":"all","relationshipControlType":null}],"enumerationPropertyControllers":[]},"viewContainer":{"soundViews":[],"descriptionViews":[],"textViews":[],"shapeViews":[],"backgroundViews":[],"imageViews":[]},"listenerContainer":{"linkListeners":[],"animationListeners":[],"bluetoothListeners":[]},"customCodeContainer":{"onProgramAddedCode":"","onProgramRemovedCode":"","onProgramChangedPositionCode":"","onProgramMarkersAddedCode":"","onProgramMarkersRemovedCode":"","onProgramMarkersChangedPositionCode":"","onProgramAdjacentCode":"","onProgramSeparatedCode":""}}]}

Credits

Brett Fiedler
1 project • 0 followers

Comments