{"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":""}}]}