コード例 #1
0
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var makeATen = require( 'MAKE_A_TEN/makeATen' );

  /**
   * @constructor
   *
   * @param {number} leftTerm
   * @param {number} rightTerm
   */
  function NumberChallenge( leftTerm, rightTerm ) {
    // @public {number} - The left-hand term for addition
    this.leftTerm = leftTerm;

    // @public {number} - The right-hand term for addition
    this.rightTerm = rightTerm;

    // This object is immutable
    Object.freeze( this );
  }

  makeATen.register( 'NumberChallenge', NumberChallenge );

  return inherit( Object, NumberChallenge );
} );
コード例 #2
0
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var makeATen = require( 'MAKE_A_TEN/makeATen' );
  var MakeATenAddingModel = require( 'MAKE_A_TEN/make-a-ten/adding/model/MakeATenAddingModel' );
  var MakeATenAddingScreenView = require( 'MAKE_A_TEN/make-a-ten/adding/view/MakeATenAddingScreenView' );
  var MakeATenConstants = require( 'MAKE_A_TEN/make-a-ten/common/MakeATenConstants' );
  var MakeATenUtil = require( 'MAKE_A_TEN/make-a-ten/common/MakeATenUtil' );
  var Property = require( 'AXON/Property' );
  var Screen = require( 'JOIST/Screen' );

  // images
  var addingHomeScreenImage = require( 'image!MAKE_A_TEN/adding-home-screen.png' );
  var addingNavBarImage = require( 'image!MAKE_A_TEN/adding-nav-bar.png' );

  // strings
  var screenAddingString = require( 'string!MAKE_A_TEN/screen.adding' );

  /**
   * @constructor
   */
  function MakeATenAddingScreen() {

    var options = {
      name: screenAddingString,
      backgroundColorProperty: new Property( MakeATenConstants.SCREEN_BACKGROUND_COLOR ),
      homeScreenIcon: MakeATenUtil.createIconWithBackgroundColor( addingHomeScreenImage, MakeATenConstants.SCREEN_BACKGROUND_COLOR ),
      navigationBarIcon: MakeATenUtil.createIconWithBackgroundColor( addingNavBarImage, MakeATenConstants.SCREEN_BACKGROUND_COLOR )
    };

    Screen.call( this,
      function() { return new MakeATenAddingModel(); },
      function( model ) { return new MakeATenAddingScreenView( model ); },
      options );
  }

  makeATen.register( 'MakeATenAddingScreen', MakeATenAddingScreen );

  return inherit( Screen, MakeATenAddingScreen );
} );
コード例 #3
0
define( function( require ) {
  'use strict';

  // modules
  var makeATen = require( 'MAKE_A_TEN/makeATen' );

  var MakeATenQueryParameters = QueryStringMachine.getAll( {

    // Initializes the Explore screen with specific numbers, spaced horizontally,
    // e.g. ?exploreNumbers=10,51, where 0 indicates none.
    exploreNumbers: {
      type: 'array',
      elementSchema: {
        type: 'number'
      },
      defaultValue: [ 10 ]
    }
  } );

  makeATen.register( 'MakeATenQueryParameters', MakeATenQueryParameters );

  return MakeATenQueryParameters;
} );
コード例 #4
0
define( function( require ) {
  'use strict';

  // modules
  var ArithmeticRules = require( 'MAKE_A_TEN/make-a-ten/common/model/ArithmeticRules' );
  var arrayRemove = require( 'PHET_CORE/arrayRemove' );
  var BaseNumber = require( 'MAKE_A_TEN/make-a-ten/common/model/BaseNumber' );
  var BaseNumberNode = require( 'MAKE_A_TEN/make-a-ten/common/view/BaseNumberNode' );
  var DragListener = require( 'SCENERY/listeners/DragListener' );
  var Emitter = require( 'AXON/Emitter' );
  var inherit = require( 'PHET_CORE/inherit' );
  var makeATen = require( 'MAKE_A_TEN/makeATen' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PaperNumber = require( 'MAKE_A_TEN/make-a-ten/common/model/PaperNumber' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );

  /**
   * @constructor
   *
   * @param {PaperNumber} paperNumber
   * @param {Property.<Bounds2>} availableViewBoundsProperty
   * @param {Function} addAndDragNumber - function( event, paperNumber ), adds and starts a drag for a number
   * @param {Function} tryToCombineNumbers - function( paperNumber ), called to combine our paper number
   */
  function PaperNumberNode( paperNumber, availableViewBoundsProperty, addAndDragNumber, tryToCombineNumbers ) {
    var self = this;

    Node.call( this );

    // @public {PaperNumber} - Our model
    this.paperNumber = paperNumber;

    // @public {Emitter} - Triggered with self when this paper number node starts to get dragged
    this.moveEmitter = new Emitter( { validators: [ { valueType: PaperNumberNode } ] } );

    // @public {Emitter} - Triggered with self when this paper number node is split
    this.splitEmitter = new Emitter( { validators: [ { valueType: PaperNumberNode } ] } );

    // @public {Emitter} - Triggered when user interaction with this paper number begins.
    this.interactionStartedEmitter = new Emitter( { validators: [ { valueType: PaperNumberNode } ] } );

    // @private {boolean} - When true, don't emit from the moveEmitter (synthetic drag)
    this.preventMoveEmit = false;

    // @private {Bounds2}
    this.availableViewBoundsProperty = availableViewBoundsProperty;

    // @private {Node} - Container for the digit image nodes
    this.numberImageContainer = new Node( {
      pickable: false
    } );
    this.addChild( this.numberImageContainer );

    // @private {Rectangle} - Hit target for the "split" behavior, where one number would be pulled off from the
    //                        existing number.
    this.splitTarget = new Rectangle( 0, 0, 100, 100, {
      cursor: 'pointer'
    } );
    this.addChild( this.splitTarget );

    // @private {Rectangle} - Hit target for the "move" behavior, which just drags the existing paper number.
    this.moveTarget = new Rectangle( 0, 0, 100, 100, {
      cursor: 'move'
    } );
    this.addChild( this.moveTarget );

    // View-coordinate offset between our position and the pointer's position, used for keeping drags synced.
    // @private {DragListener}
    this.moveDragHandler = new DragListener( {
      targetNode: this,
      start: function( event, listener ) {
        self.interactionStartedEmitter.emit( self );
        if ( !self.preventMoveEmit ) {
          self.moveEmitter.emit( self );
        }
      },

      drag: function( event, listener ) {
        paperNumber.setConstrainedDestination( availableViewBoundsProperty.value, listener.parentPoint );
      },

      end: function( event, listener ) {
        tryToCombineNumbers( self.paperNumber );
        paperNumber.endDragEmitter.emit( paperNumber );
      }
    } );
    this.moveDragHandler.isUserControlledProperty.link( function( controlled ) {
      paperNumber.userControlledProperty.value = controlled;
    } );
    this.moveTarget.addInputListener( this.moveDragHandler );

    // @private {Object}
    this.splitDragHandler = {
      down: function( event ) {
        if ( !event.canStartPress() ) { return; }

        var viewPosition = self.globalToParentPoint( event.pointer.point );

        // Determine how much (if any) gets moved off
        var pulledPlace = paperNumber.getBaseNumberAt( self.parentToLocalPoint( viewPosition ) ).place;
        var amountToRemove = ArithmeticRules.pullApartNumbers( paperNumber.numberValueProperty.value, pulledPlace );
        var amountRemaining = paperNumber.numberValueProperty.value - amountToRemove;

        // it cannot be split - so start moving
        if ( !amountToRemove ) {
          self.startSyntheticDrag( event );
          return;
        }

        paperNumber.changeNumber( amountRemaining );

        self.interactionStartedEmitter.emit( self );
        self.splitEmitter.emit( self );

        var newPaperNumber = new PaperNumber( amountToRemove, paperNumber.positionProperty.value );
        addAndDragNumber( event, newPaperNumber );
      }
    };
    this.splitTarget.addInputListener( this.splitDragHandler );

    // @private {Function} - Listener that hooks model position to view translation.
    this.translationListener = function( position ) {
      self.translation = position;
    };

    // @private {Function} - Listener for when our number changes
    this.updateNumberListener = this.updateNumber.bind( this );

    // @private {Function} - Listener reference that gets attached/detached. Handles moving the Node to the front.
    this.userControlledListener = function( userControlled ) {
      if ( userControlled ) {
        self.moveToFront();
      }
    };
  }

  makeATen.register( 'PaperNumberNode', PaperNumberNode );

  return inherit( Node, PaperNumberNode, {
    /**
     * Rebuilds the image nodes that display the actual paper number, and resizes the mouse/touch targets.
     * @private
     */
    updateNumber: function() {
      var self = this;

      var reversedBaseNumbers = this.paperNumber.baseNumbers.slice().reverse();
      // Reversing allows easier opacity computation and has the nodes in order for setting children.
      this.numberImageContainer.children = _.map( reversedBaseNumbers, function( baseNumber, index ) {
        // each number has successively less opacity on top
        return new BaseNumberNode( baseNumber, 0.95 * Math.pow( 0.97, index ) );
      } );

      // Grab the bounds of the biggest base number for the full bounds
      var fullBounds = this.paperNumber.baseNumbers[ this.paperNumber.baseNumbers.length - 1 ].bounds;

      // Split target only visible if our number is > 1. Move target can resize as needed.
      if ( this.paperNumber.numberValueProperty.value === 1 ) {
        self.splitTarget.visible = false;
        self.moveTarget.mouseArea = self.moveTarget.touchArea = self.moveTarget.rectBounds = fullBounds;
      }
      else {
        self.splitTarget.visible = true;

        // Locate the boundary between the "move" input area and "split" input area.
        var boundaryY = this.paperNumber.getBoundaryY();

        // Modify our move/split targets
        self.moveTarget.mouseArea = self.moveTarget.touchArea = self.moveTarget.rectBounds = fullBounds.withMinY( boundaryY );
        self.splitTarget.mouseArea = self.splitTarget.touchArea = self.splitTarget.rectBounds = fullBounds.withMaxY( boundaryY );
      }

      // Changing the number must have happened from an interaction. If combined, we want to put cues on this.
      this.interactionStartedEmitter.emit( this );
    },

    /**
     * Called when we grab an event from a different input (like clicking the paper number in the explore panel, or
     * splitting paper numbers), and starts a drag on this paper number.
     * @public
     *
     * @param {Event} event - Scenery event from the relevant input handler
     */
    startSyntheticDrag: function( event ) {
      // Don't emit a move event, as we don't want the cue to disappear.
      this.preventMoveEmit = true;
      this.moveDragHandler.press( event );
      this.preventMoveEmit = false;
    },

    /**
     * Implements the API for ClosestDragListener.
     * @public
     *
     * @param {Event} event - Scenery event from the relevant input handler
     */
    startDrag: function( event ) {
      if ( this.globalToLocalPoint( event.pointer.point ).y < this.splitTarget.bottom && this.paperNumber.numberValueProperty.value > 1 ) {
        this.splitDragHandler.down( event );
      }
      else {
        this.moveDragHandler.press( event );
      }
    },

    /**
     * Implements the API for ClosestDragListener.
     * @public
     *
     * @param {Vector2} globalPoint
     */
    computeDistance: function( globalPoint ) {
      if ( this.paperNumber.userControlledProperty.value ) {
        return Number.POSITIVE_INFINITY;
      }
      else {
        var globalBounds = this.localToGlobalBounds( this.paperNumber.getLocalBounds() );
        return Math.sqrt( globalBounds.minimumDistanceToPointSquared( globalPoint ) );
      }
    },

    /**
     * Attaches listeners to the model. Should be called when added to the scene graph.
     * @public
     */
    attachListeners: function() {
      // mirrored unlinks in detachListeners()
      this.paperNumber.userControlledProperty.link( this.userControlledListener );
      this.paperNumber.numberValueProperty.link( this.updateNumberListener );
      this.paperNumber.positionProperty.link( this.translationListener );
    },

    /**
     * Removes listeners from the model. Should be called when removed from the scene graph.
     * @public
     */
    detachListeners: function() {
      this.paperNumber.positionProperty.unlink( this.translationListener );
      this.paperNumber.numberValueProperty.unlink( this.updateNumberListener );
      this.paperNumber.userControlledProperty.unlink( this.userControlledListener );
    },

    /**
     * Find all nodes which are attachable to the dragged node. This method is called once the user ends the dragging.
     * @public
     *
     * @param {Array.<PaperNumberNode>} allPaperNumberNodes
     * @returns {Array}
     */
    findAttachableNodes: function( allPaperNumberNodes ) {
      var self = this;
      var attachableNodeCandidates = allPaperNumberNodes.slice();
      arrayRemove( attachableNodeCandidates, this );

      return attachableNodeCandidates.filter( function( candidateNode ) {
        return PaperNumber.arePaperNumbersAttachable( self.paperNumber, candidateNode.paperNumber );
      } );
    }

  }, {
    /**
     * Given a number's digit and place, looks up the associated image.
     * @public
     *
     * @param {number} digit
     * @param {number} place
     * @returns {HTMLImageElement}
     */
    getNumberImage: function( digit, place ) {
      return new BaseNumberNode( new BaseNumber( digit, place ), 1 );
    }
  } );
} );
コード例 #5
0
define( function( require ) {
  'use strict';

  // modules
  var ActiveTerm = require( 'MAKE_A_TEN/make-a-ten/adding/model/ActiveTerm' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var makeATen = require( 'MAKE_A_TEN/makeATen' );
  var MakeATenConstants = require( 'MAKE_A_TEN/make-a-ten/common/MakeATenConstants' );
  var MathSymbols = require( 'SCENERY_PHET/MathSymbols' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Text = require( 'SCENERY/nodes/Text' );

  // constants
  var EQUATION_FONT = new PhetFont( { size: 45, weight: 'bold' } );
  var STROKE_COLOR = '#000';
  var LAYOUT_MULTIPLIER = 1 / 8; // Fraction offset of the text from the background's border

  /**
   * @constructor
   *
   * @param {AdditionTerms} additionTerms - Our model, contains information about the left/right and active terms
   * @param {boolean} highlightBorders - Whether there should be highlighted borders around the active term.
   */
  function AdditionTermsNode( additionTerms, highlightBorders ) {
    Node.call( this );

    var backgroundOptions = {
      stroke: STROKE_COLOR,
      lineDash: [ 5, 5 ],
      lineWidth: 2,
      visible: highlightBorders
    };

    var leftNumberDisplayBackground = new Rectangle( 0, 0, 100, 78, 10, 10, backgroundOptions );
    var rightNumberDisplayBackground = new Rectangle( 0, 0, 100, 78, 10, 10, backgroundOptions );

    var plusText = new Text( MathSymbols.PLUS, { font: EQUATION_FONT, fill: MakeATenConstants.EQUATION_FILL } );
    var equalsSignText = new Text( MathSymbols.EQUAL_TO, { font: EQUATION_FONT, fill: MakeATenConstants.EQUATION_FILL } );

    var numberDisplayBox = new HBox( {
      children: [ leftNumberDisplayBackground, plusText, rightNumberDisplayBackground ],
      spacing: 5,
      resize: false // since we toggle the stroke
    } );

    var leftTermText = new Text( '', { font: EQUATION_FONT, fill: MakeATenConstants.EQUATION_FILL } );
    var rightTermText = new Text( '', { font: EQUATION_FONT, fill: MakeATenConstants.EQUATION_FILL } );

    this.addChild( numberDisplayBox );
    this.addChild( leftTermText );
    this.addChild( rightTermText );
    this.addChild( equalsSignText );

    function layout() {
      if ( !rightTermText.bounds.isEmpty() ) {
        equalsSignText.left = rightTermText.right + 20;
      }
      if ( !leftTermText.bounds.isEmpty() ) {
        leftTermText.right = leftNumberDisplayBackground.right - leftNumberDisplayBackground.width * LAYOUT_MULTIPLIER;
      }
    }

    additionTerms.leftTermProperty.link( function( term ) {
      leftTermText.text = term ? term : '';
      layout();
    } );

    additionTerms.rightTermProperty.link( function( term ) {
      rightTermText.text = term ? term : '';
      layout();
    } );

    // Add highlights if applicable
    if ( highlightBorders ) {
      Property.multilink( [ additionTerms.leftTermProperty, additionTerms.activeTermProperty ], function( leftTerm, activeTerm ) {
        leftNumberDisplayBackground.stroke = ( leftTerm === 0 || activeTerm === ActiveTerm.LEFT ) ? STROKE_COLOR : null;
        leftNumberDisplayBackground.fill = activeTerm === ActiveTerm.LEFT ? 'white' : null;
      } );
      Property.multilink( [ additionTerms.rightTermProperty, additionTerms.activeTermProperty ], function( rightTerm, activeTerm ) {
        rightNumberDisplayBackground.stroke = ( rightTerm === 0 || activeTerm === ActiveTerm.RIGHT ) ? STROKE_COLOR : null;
        rightNumberDisplayBackground.fill = activeTerm === ActiveTerm.RIGHT ? 'white' : null;
      } );

      Property.multilink( [ additionTerms.leftTermProperty, additionTerms.rightTermProperty, additionTerms.activeTermProperty ], function() {
        equalsSignText.visible = additionTerms.hasBothTerms();
      } );
    }

    // Center everything vertically
    var centerY = numberDisplayBox.centerY;
    leftTermText.centerY = centerY;
    rightTermText.centerY = centerY;
    equalsSignText.centerY = centerY;

    // Unchanging layout position of the right text node
    rightTermText.left = rightNumberDisplayBackground.left + rightNumberDisplayBackground.width * LAYOUT_MULTIPLIER;

    // @public
    this.getLeftAlignment = function() {
      return leftTermText.right;
    };
    this.getRightAlignment = function() {
      return rightTermText.left;
    };
  }

  makeATen.register( 'AdditionTermsNode', AdditionTermsNode );

  return inherit( Node, AdditionTermsNode );
} );
コード例 #6
0
ファイル: SplitCueNode.js プロジェクト: phetsims/making-tens
define( function( require ) {
  'use strict';

  // modules
  var ArrowNode = require( 'SCENERY_PHET/ArrowNode' );
  var Color = require( 'SCENERY/util/Color' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var makeATen = require( 'MAKE_A_TEN/makeATen' );
  var MakeATenConstants = require( 'MAKE_A_TEN/make-a-ten/common/MakeATenConstants' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );

  // images
  var handImage = require( 'image!SCENERY_PHET/hand.png' );

  /**
   * @constructor
   *
   * @param {Cue} cue - Our cue model
   */
  function SplitCueNode( cue ) {
    Node.call( this, {
      pickable: false,
      usesOpacity: true
    } );

    // @private {Cue}
    this.cue = cue;

    var arrowOptions = {
      fill: MakeATenConstants.CUE_FILL,
      stroke: null,
      headHeight: 14,
      headWidth: 22,
      tailWidth: 9,
      x: 7,
      y: 3
    };

    this.seeThroughRectangle = new Rectangle( 0, 0, 100, 100, {
      fill: new Color( MakeATenConstants.CUE_FILL ).withAlpha( 0.2 )
    } );
    this.addChild( this.seeThroughRectangle );

    this.arrowContainer = new Node( {
      children: [
        new ArrowNode( 0, 0, 30, -30, arrowOptions ),
        new Image( handImage, {
          scale: 0.3,
          rotation: Math.PI / 6 - Math.PI / 5
        } )
      ]
    } );
    this.addChild( this.arrowContainer );

    var updatePositionListener = this.updatePosition.bind( this );
    var updateRectangleListener = this.updateRectangle.bind( this );

    cue.visibilityProperty.linkAttribute( this, 'visible' );
    cue.opacityProperty.linkAttribute( this, 'opacity' );
    cue.visibilityProperty.link( updatePositionListener ); // update position when we become visible
    cue.paperNumberProperty.link( function( newPaperNumber, oldPaperNumber ) {
      if ( newPaperNumber ) {
        newPaperNumber.positionProperty.link( updatePositionListener ); // translation
        newPaperNumber.numberValueProperty.link( updatePositionListener ); // may have changed bounds
        newPaperNumber.numberValueProperty.link( updateRectangleListener ); // may have changed bounds
      }
      if ( oldPaperNumber ) {
        oldPaperNumber.numberValueProperty.unlink( updateRectangleListener );
        oldPaperNumber.numberValueProperty.unlink( updatePositionListener );
        oldPaperNumber.positionProperty.unlink( updatePositionListener );
      }
    } );
  }

  makeATen.register( 'SplitCueNode', SplitCueNode );

  return inherit( Node, SplitCueNode, {
    /**
     * Updates the position of the cue.
     * @private
     */
    updatePosition: function() {
      var visible = this.cue.visibilityProperty.value;
      var paperNumber = this.cue.paperNumberProperty.value;

      if ( visible && paperNumber ) {
        var position = paperNumber.positionProperty.value;
        var localBounds = paperNumber.getLocalBounds();
        this.setTranslation( position );
        this.arrowContainer.setTranslation( localBounds.right - 22, localBounds.top + 15 );
      }
    },

    /**
     * Updates the size of the semi-transparent rectangle.
     * @private
     */
    updateRectangle: function() {
      var paperNumber = this.cue.paperNumberProperty.value;

      if ( paperNumber ) {
        var bounds = paperNumber.getLocalBounds();
        var boundaryY = paperNumber.getBoundaryY();
        this.seeThroughRectangle.setRectBounds( bounds.withMaxY( boundaryY ) );
      }
    }
  } );
} );
コード例 #7
0
ファイル: PaperNumber.js プロジェクト: phetsims/making-tens
define( function( require ) {
  'use strict';

  // modules
  var BaseNumber = require( 'MAKE_A_TEN/make-a-ten/common/model/BaseNumber' );
  var BooleanProperty = require( 'AXON/BooleanProperty' );
  var Bounds2 = require( 'DOT/Bounds2' );
  var Emitter = require( 'AXON/Emitter' );
  var inherit = require( 'PHET_CORE/inherit' );
  var makeATen = require( 'MAKE_A_TEN/makeATen' );
  var MakeATenConstants = require( 'MAKE_A_TEN/make-a-ten/common/MakeATenConstants' );
  var MakeATenUtil = require( 'MAKE_A_TEN/make-a-ten/common/MakeATenUtil' );
  var NumberProperty = require( 'AXON/NumberProperty' );
  var Vector2 = require( 'DOT/Vector2' );
  var Vector2Property = require( 'DOT/Vector2Property' );

  // Incremented for PaperNumber IDs
  var nextPaperNumberId = 1;

  /**
   * @constructor
   *
   * @param {number} numberValue - Numeric value, e.g. 123
   * @param {Vector2} initialPosition
   */
  function PaperNumber( numberValue, initialPosition ) {
    // @public {number} - IDs required for map-like lookup, see https://github.com/phetsims/make-a-ten/issues/199
    this.id = nextPaperNumberId++;

    // @public {NumberProperty} - The number that this model represents, e.g. 324
    this.numberValueProperty = new NumberProperty( numberValue );

    // @public Property that indicates where in model space the upper left corner of this shape is. In general, this
    // should not be set directly outside of this type, and should be manipulated through the methods defined below.
    this.positionProperty = new Vector2Property( initialPosition.copy() );

    // @public {BooleanProperty} - Flag that tracks whether the user is dragging this number around. Should be set
    //                             externally, generally by the view node.
    this.userControlledProperty = new BooleanProperty( false );

    // @public {Vector2} - Destination is used for animation, and should be set through accessor methods only.
    this.destination = initialPosition.copy(); // @private

    // @public {boolean} - Whether this element is animating from one location to another, do not set externally.
    this.animating = false;

    // @public {Array.<BaseNumber>} - Represents the non-zero place values in this number. 1034 will have three place
    //                                values, 4, 30 and 1000, which when summed will equal our number.
    this.baseNumbers = PaperNumber.getBaseNumbers( this.numberValueProperty.value );

    // @public {Emitter} - Fires when the user stops dragging a paper number.
    this.endDragEmitter = new Emitter( { validators: [ { valueType: PaperNumber } ] } );

    // @public {Emitter} - Fires when the animation towards our destination ends (we hit our destination).
    this.endAnimationEmitter = new Emitter( { validators: [ { valueType: PaperNumber } ] } );
  }

  makeATen.register( 'PaperNumber', PaperNumber );

  return inherit( Object, PaperNumber, {
    /**
     * Animates the number towards its destination.
     * @public
     *
     * @param {number} dt
     */
    step: function( dt ) {
      if ( !this.userControlledProperty.value ) {
        var currentPosition = this.positionProperty.value;
        assert && assert( currentPosition.isFinite() );
        assert && assert( this.destination.isFinite() );

        // perform any animation
        var distanceToDestination = currentPosition.distance( this.destination );
        if ( distanceToDestination > dt * MakeATenConstants.ANIMATION_VELOCITY ) {
          // Move a step toward the destination.
          var stepVector = this.destination.minus( currentPosition ).setMagnitude( MakeATenConstants.ANIMATION_VELOCITY * dt );
          assert && assert( stepVector.isFinite() );
          this.positionProperty.value = currentPosition.plus( stepVector );

        }
        else if ( this.animating ) {
          // Less than one time step away, so just go to the destination.
          this.positionProperty.value = this.destination;
          this.animating = false;
          this.endAnimationEmitter.emit( this );
        }
      }
    },

    /**
     * The number of digits in the number, including zeros, e.g. 1204 has 4 digits.
     * @public
     *
     * @returns {number}
     */
    get digitLength() {
      assert && assert( this.numberValueProperty.value > 0 );

      return MakeATenUtil.digitsInNumber( this.numberValueProperty.value );
    },

    /**
     * Returns the bounds of the paper number relative to the paper number's origin.
     * @public
     *
     * @returns {Bounds2}
     */
    getLocalBounds: function() {
      // Use the largest base number
      return this.baseNumbers[ this.baseNumbers.length - 1 ].bounds;
    },

    /**
     * Locate the boundary between the "move" input area and "split" input area, in the number's local bounds.
     * @public
     *
     * @returns {Bounds2}
     */
    getBoundaryY: function() {
      var bounds = this.getLocalBounds();
      var moveToSplitRatio = MakeATenConstants.SPLIT_BOUNDARY_HEIGHT_PROPORTION;
      return bounds.maxY * ( 1 - moveToSplitRatio ) + bounds.minY * moveToSplitRatio;
    },

    /**
     * Returns the ideal spot to "drag" a number from (near the center of its move target) relative to its origin.
     * @public
     *
     * @returns {Vector2}
     */
    getDragTargetOffset: function() {
      var bounds = this.getLocalBounds();

      var ratio = MakeATenConstants.SPLIT_BOUNDARY_HEIGHT_PROPORTION / 2;
      return new Vector2( bounds.centerX, bounds.minY * ratio + bounds.maxY * ( 1 - ratio ) );
    },

    /**
     * Changes the number that this paper number represents.
     * @public
     *
     * @param {number} numberValue
     */
    changeNumber: function( numberValue ) {
      assert && assert( typeof numberValue === 'number' );

      this.baseNumbers = PaperNumber.getBaseNumbers( numberValue );
      this.numberValueProperty.value = numberValue;
    },

    /**
     * Sets the destination of the number. If animate is false, it also sets the position.
     * @public
     *
     * @param {Vector2} destination
     * @param {boolean} animate - Whether to animate. If true, it will slide towards the destination. If false, it will
     *                            immediately set the position to be the same as the destination.
     */
    setDestination: function( destination, animate ) {
      assert && assert( destination.isFinite() );

      this.destination = destination;

      if ( animate ) {
        this.animating = true;
      }
      else {
        this.positionProperty.value = destination;
      }
    },

    /**
     * If our paper number is outside of the available view bounds, move in inside those bounds.
     * @public
     *
     * @param {Bounds2} viewBounds
     * @param {Vector2} position
     * @param {boolean} [animate] - Indicates if the new constrained position should be directly set or animated
     */
    setConstrainedDestination: function( viewBounds, newDestination, animate ) {
      // Determine how our number's origin can be placed in the bounds
      var localBounds = this.getLocalBounds();
      var padding = 10;
      var originBounds = new Bounds2( viewBounds.left - localBounds.left,
                                      viewBounds.top - localBounds.top,
                                      viewBounds.right - localBounds.right,
                                      viewBounds.bottom - localBounds.bottom ).eroded( padding );
      this.setDestination( originBounds.closestPointTo( newDestination ), animate );
    },

    /**
     * Returns the lowest place number whose bounds include the position.
     * @public
     *
     * @param {Vector2} position - Position relative to this number's origin.
     * @returns {BaseNumber}
     */
    getBaseNumberAt: function( position ) {
      for ( var i = 0; i < this.baseNumbers.length; i++ ) {
        assert && assert( i === 0 || this.baseNumbers[ i ].place > this.baseNumbers[ i - 1 ].place,
          'Ensure that we start at lower places, required for this to work properly' );

        var baseNumber = this.baseNumbers[ i ];

        if ( baseNumber.bounds.containsPoint( position ) ) {
          return baseNumber;
        }
      }

      // Outside of the bounds, so we need to check each and determine the closest.
      for ( i = 0; i < this.baseNumbers.length; i++ ) {
        baseNumber = this.baseNumbers[ i ];
        if ( position.x > baseNumber.bounds.left ) {
          return baseNumber;
        }
      }

      // Default the largest one.
      return this.baseNumbers[ this.baseNumbers.length - 1 ];
    }
  }, {
    /**
     * Given a number, returns an array of BaseNumbers that will represent the digit places.
     * @public
     *
     * @param {number} number - The number we want to break into digit places.
     * @returns {Array.<BaseNumber>}
     */
    getBaseNumbers: function( number ) {
      assert && assert( number > 0 && number % 1 === 0 );

      var result = [];

      // Divide by 10 each loop, using the remainder and place location to create the place numbers.
      var remainder = number;
      var place = 0;
      while ( remainder ) {
        var digit = remainder % 10;
        if ( digit ) {
          result.push( new BaseNumber( digit, place ) );
        }

        remainder = ( remainder - digit ) / 10;
        place++;
      }

      return result;
    },

    /**
     * Returns whether the two paper numbers are close enough to be "attached" to each other.
     * @public
     *
     * @param {PaperNumber} paperNumber1
     * @param {PaperNumber} paperNumber2
     * @returns {boolean}
     */
    arePaperNumbersAttachable: function( paperNumber1, paperNumber2 ) {
      var firstLarger = paperNumber1.numberValueProperty.value > paperNumber2.numberValueProperty.value;
      var largePaperNumber = firstLarger ? paperNumber1 : paperNumber2;
      var smallPaperNumber = firstLarger ? paperNumber2 : paperNumber1;

      var smallCenter = smallPaperNumber.positionProperty.value.plus( smallPaperNumber.getLocalBounds().center );
      var largePosition = largePaperNumber.positionProperty.value;
      var largeBounds = largePaperNumber.getLocalBounds().shifted( largePosition.x, largePosition.y );

      var unitX = ( smallCenter.x - largeBounds.centerX ) / ( largeBounds.width / 2 );
      var unitY = ( smallCenter.y - largeBounds.centerY ) / ( largeBounds.height / 2 );

      return unitX * unitX + 2 * unitY * unitY < 1;
    }
  } );
} );
コード例 #8
0
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var makeATen = require( 'MAKE_A_TEN/makeATen' );
  var MakeATenConstants = require( 'MAKE_A_TEN/make-a-ten/common/MakeATenConstants' );
  var ObservableArray = require( 'AXON/ObservableArray' );
  var PaperNumber = require( 'MAKE_A_TEN/make-a-ten/common/model/PaperNumber' );
  var Vector2 = require( 'DOT/Vector2' );

  /**
   * @constructor
   */
  function MakeATenCommonModel() {
    // @public {ObservableArray.<PaperNumber>} - Numbers in play that can be interacted with.
    this.paperNumbers = new ObservableArray();
  }

  makeATen.register( 'MakeATenCommonModel', MakeATenCommonModel );

  return inherit( Object, MakeATenCommonModel, {
    /**
     * Steps the model forward by a unit of time.
     *
     * @param {number} dt
     */
    step: function( dt ) {
      // Cap large dt values, which can occur when the tab containing
      // the sim had been hidden and then re-shown
      dt = Math.min( 0.1, dt );

      for ( var i = 0; i < this.paperNumbers.length; i++ ) {
        this.paperNumbers.get( i ).step( dt );
      }
    },

    /**
     * Given two paper numbers, combine them (set one's value to the sum of their previous values, and remove the
     * other).
     *
     * @param {Bounds2} availableModelBounds - Constrain the location to be inside these bounds
     * @param {PaperNumber} draggedPaperNumber
     * @param {PaperNumber} dropTargetNumber
     */
    collapseNumberModels: function( availableModelBounds, draggedPaperNumber, dropTargetNumber ) {
      var dropTargetNumberValue = dropTargetNumber.numberValueProperty.value;
      var draggedNumberValue = draggedPaperNumber.numberValueProperty.value;
      var newValue = dropTargetNumberValue + draggedNumberValue;

      var numberToRemove;
      var numberToChange;

      // See https://github.com/phetsims/make-a-ten/issues/260
      if ( draggedPaperNumber.digitLength === dropTargetNumber.digitLength ) {
        numberToRemove = draggedPaperNumber;
        numberToChange = dropTargetNumber;
      }
      else {
        // The larger number gets changed, the smaller one gets removed.
        var droppingOnLarger = dropTargetNumberValue > draggedNumberValue;
        numberToRemove = droppingOnLarger ? draggedPaperNumber : dropTargetNumber;
        numberToChange = droppingOnLarger ? dropTargetNumber : draggedPaperNumber;
      }

      // Apply changes
      this.removePaperNumber( numberToRemove );
      numberToChange.changeNumber( newValue );
      numberToChange.setConstrainedDestination( availableModelBounds, numberToChange.positionProperty.value, false );
    },

    /**
     * Add a PaperNumber to the model
     * @public
     *
     * @param {PaperNumber} paperNumber
     */
    addPaperNumber: function( paperNumber ) {
      this.paperNumbers.push( paperNumber );
    },

    /**
     * Remove a PaperNumber from the model
     * @public
     *
     * @param {PaperNumber} paperNumber
     */
    removePaperNumber: function( paperNumber ) {
      this.paperNumbers.remove( paperNumber );
    },

    /**
     * Remove all PaperNumbers from the model.
     * @public
     *
     * @param {PaperNumber} paperNumber
     */
    removeAllPaperNumbers: function() {
      this.paperNumbers.clear();
    },

    /**
     * Given an array of integers, create and add paper numbers for each that are evenly distributed across the screen.
     * @public
     *
     * @param {Array.<number>} numbers
     */
    addMultipleNumbers: function( numbers ) {
      for ( var i = 0; i < numbers.length; i++ ) {
        var number = numbers[ i ];

        // Ingore 0s
        if ( !number ) { continue; }

        // evenly distribute across the screen
        var x = MakeATenConstants.LAYOUT_BOUNDS.width * ( 1 + i ) / ( numbers.length + 1 );
        var initialNumberPosition = new Vector2( x, MakeATenConstants.LAYOUT_BOUNDS.height / 2.5 );
        var paperNumber = new PaperNumber( number, initialNumberPosition );
        this.addPaperNumber( paperNumber );
      }
    },

    /**
     * @param {Bounds2} availableModelBounds - Constrain the location to be inside these bounds
     * @param {PaperNumber} paperNumber1
     * @param {PaperNumber} paperNumber2
     */
    repelAway: function( availableModelBounds, paperNumber1, paperNumber2 ) {
      // Determine which are 'left' and 'right'
      var isPaper1Left = paperNumber1.positionProperty.value.x < paperNumber2.positionProperty.value.x;
      var leftPaperNumber = isPaper1Left ? paperNumber1 : paperNumber2;
      var rightPaperNumber = isPaper1Left ? paperNumber2 : paperNumber1;

      // Determine offsets
      var repelLeftOffset = -MakeATenConstants.MOVE_AWAY_DISTANCE[ leftPaperNumber.digitLength ];
      var repelRightOffset = MakeATenConstants.MOVE_AWAY_DISTANCE[ rightPaperNumber.digitLength ];
      var leftPosition = leftPaperNumber.positionProperty.value.plusXY( repelLeftOffset, 0 );
      var rightPosition = rightPaperNumber.positionProperty.value.plusXY( repelRightOffset, 0 );

      // Kick off the animation to the destination
      var animateToDestination = true;
      leftPaperNumber.setConstrainedDestination( availableModelBounds, leftPosition, animateToDestination );
      rightPaperNumber.setConstrainedDestination( availableModelBounds, rightPosition, animateToDestination );
    },

    /**
     * Reset the model
     * @public
     */
    reset: function() {
      this.removeAllPaperNumbers();
    }
  } );
} );
コード例 #9
0
define( function( require ) {
  'use strict';

  // modules
  var BooleanProperty = require( 'AXON/BooleanProperty' );
  var Checkbox = require( 'SUN/Checkbox' );
  var ExplorePanel = require( 'MAKE_A_TEN/make-a-ten/explore/view/ExplorePanel' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var makeATen = require( 'MAKE_A_TEN/makeATen' );
  var MakeATenCommonView = require( 'MAKE_A_TEN/make-a-ten/common/view/MakeATenCommonView' );
  var MakeATenConstants = require( 'MAKE_A_TEN/make-a-ten/common/MakeATenConstants' );
  var MathSymbols = require( 'SCENERY_PHET/MathSymbols' );
  var PaperNumber = require( 'MAKE_A_TEN/make-a-ten/common/model/PaperNumber' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var SplitCueNode = require( 'MAKE_A_TEN/make-a-ten/explore/view/SplitCueNode' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Vector2 = require( 'DOT/Vector2' );

  // strings
  var hideTotalString = require( 'string!MAKE_A_TEN/hideTotal' );

  // constants
  var EQUATION_FONT = new PhetFont( { size: 60, weight: 'bold' } );

  /**
   * @param {MakeATenExploreModel} model
   * @constructor
   */
  function MakeATenExploreScreenView( model ) {
    var self = this;

    // @private {Function} - Called with function( paperNumberNode ) on number splits
    this.numberSplitListener = this.onNumberSplit.bind( this );

    // @private {Function} - Called with function( paperNumberNode ) when a number begins to be interacted with.
    this.numberInteractionListener = this.onNumberInteractionStarted.bind( this );

    // @private {Function} - Called with function( paperNumber ) when a number finishes animation
    this.numberAnimationFinishedListener = this.onNumberAnimationFinished.bind( this );

    // @private {Function} - Called with function( paperNumber ) when a number finishes being dragged
    this.numberDragFinishedListener = this.onNumberDragFinished.bind( this );

    MakeATenCommonView.call( this, model );

    // @private {BooleanProperty} - Whether the total (sum) is hidden
    this.hideSumProperty = new BooleanProperty( false );

    var sumText = new Text( '0', { font: EQUATION_FONT, fill: MakeATenConstants.EQUATION_FILL } );
    model.sumProperty.linkAttribute( sumText, 'text' );

    // @private {HBox} - Displays the sum of our numbers and an equals sign, e.g. "256 ="
    this.sumNode = new HBox( {
      children: [
        sumText,
        new Text( MathSymbols.EQUAL_TO, { font: EQUATION_FONT, fill: MakeATenConstants.EQUATION_FILL } )
      ], spacing: 15
    } );

    this.addChild( this.sumNode );

    // @private {ExplorePanel} - Shows 100,10,1 that can be dragged.
    this.explorePanel = new ExplorePanel( this, model.sumProperty );
    this.addChild( this.explorePanel );

    var hideSumText = new Text( hideTotalString, {
      maxWidth: 150,
      font: new PhetFont( {
        size: 25,
        weight: 'bold'
      } ),
      fill: 'black'
    } );

    // @private {Checkbox} - When checked, hides the sum in the upper-left
    this.hideSumCheckbox = new Checkbox( hideSumText, this.hideSumProperty, {
      spacing: 10,
      boxWidth: 30
    } );
    this.hideSumCheckbox.touchArea = this.hideSumCheckbox.localBounds.dilatedXY( 10, 4 );
    this.addChild( this.hideSumCheckbox );

    this.hideSumProperty.link( function( hideSum ) {
      self.sumNode.visible = !hideSum;
    } );

    this.addChild( this.paperNumberLayerNode );

    this.addChild( new SplitCueNode( model.splitCue ) );

    this.layoutControls();
  }

  makeATen.register( 'MakeATenExploreScreenView', MakeATenExploreScreenView );

  return inherit( MakeATenCommonView, MakeATenExploreScreenView, {
    /**
     * @override
     */
    layoutControls: function() {
      MakeATenCommonView.prototype.layoutControls.call( this );

      var visibleBounds = this.visibleBoundsProperty.value;

      this.explorePanel.centerX = visibleBounds.centerX;
      this.explorePanel.bottom = visibleBounds.bottom - 10;

      this.hideSumCheckbox.left = this.explorePanel.right + 20;
      this.hideSumCheckbox.bottom = visibleBounds.bottom - 10;

      this.sumNode.left = visibleBounds.left + 30;
      this.sumNode.top = visibleBounds.top + 30;
    },

    /**
     * Whether the paper number is predominantly over the explore panel (should be collected).
     * @private
     *
     * @param {PaperNumber} paperNumber
     * @returns {boolean}
     */
    isNumberInReturnZone: function( paperNumber ) {
      // Compute the local point on the number that would need to go into the return zone.
      // This point is a bit farther down than the exact center, as it was annoying to "miss" the return zone
      // slightly by being too high (while the mouse WAS in the return zone).
      var localBounds = paperNumber.getLocalBounds();
      var localReturnPoint = localBounds.center.plus( localBounds.centerBottom ).dividedScalar( 2 );

      // And the bounds of our panel
      var panelBounds = this.explorePanel.bounds.withMaxY( this.visibleBoundsProperty.value.bottom );

      // View coordinate of our return point
      var paperCenter = paperNumber.positionProperty.value.plus( localReturnPoint );

      return panelBounds.containsPoint( paperCenter );
    },

    /**
     * @override
     */
    onPaperNumberAdded: function( paperNumber ) {
      var paperNumberNode = MakeATenCommonView.prototype.onPaperNumberAdded.call( this, paperNumber );

      // Add listeners
      paperNumberNode.splitEmitter.addListener( this.numberSplitListener );
      paperNumberNode.interactionStartedEmitter.addListener( this.numberInteractionListener );
      paperNumber.endAnimationEmitter.addListener( this.numberAnimationFinishedListener );
      paperNumber.endDragEmitter.addListener( this.numberDragFinishedListener );
    },

    /**
     * @override
     */
    onPaperNumberRemoved: function( paperNumber ) {
      var paperNumberNode = this.findPaperNumberNode( paperNumber );

      // Remove listeners
      paperNumber.endDragEmitter.removeListener( this.numberDragFinishedListener );
      paperNumber.endAnimationEmitter.removeListener( this.numberAnimationFinishedListener );
      paperNumberNode.interactionStartedEmitter.removeListener( this.numberInteractionListener );
      paperNumberNode.splitEmitter.removeListener( this.numberSplitListener );

      // Detach any attached cues
      if ( this.model.splitCue.paperNumberProperty.value === paperNumber ) {
        this.model.splitCue.detach();
      }

      MakeATenCommonView.prototype.onPaperNumberRemoved.call( this, paperNumber );
    },

    /**
     * Called when a paper number node is split.
     * @private
     *
     * @param {PaperNumberNode} paperNumberNode
     */
    onNumberSplit: function( paperNumberNode ) {
      this.model.splitCue.triggerFade();
    },

    /**
     * Called when a paper number node starts being interacted with.
     * @private
     *
     * @param {PaperNumberNode} paperNumberNode
     */
    onNumberInteractionStarted: function( paperNumberNode ) {
      var paperNumber = paperNumberNode.paperNumber;
      if ( paperNumber.numberValueProperty.value > 1 ) {
        this.model.splitCue.attachToNumber( paperNumber );
      }
    },

    /**
     * Called when a paper number has finished animating to its destination.
     * @private
     *
     * @param {PaperNumber} paperNumber
     */
    onNumberAnimationFinished: function( paperNumber ) {
      // If it animated to the return zone, it's probably split and meant to be returned.
      if ( this.isNumberInReturnZone( paperNumber ) ) {
        this.model.removePaperNumber( paperNumber );
      }
    },

    /**
     * Called when a paper number has finished being dragged.
     * @private
     *
     * @param {PaperNumber} paperNumber
     */
    onNumberDragFinished: function( paperNumber ) {
      // Return it to the panel if it's been dropped in the panel.
      if ( this.isNumberInReturnZone( paperNumber ) ) {
        var baseNumbers = paperNumber.baseNumbers;

        // Split it into a PaperNumber for each of its base numbers, and animate them to their targets in the
        // explore panel.
        for ( var i = baseNumbers.length - 1; i >= 0; i-- ) {
          var baseNumber = baseNumbers[ i ];
          var basePaperNumber = new PaperNumber( baseNumber.numberValue, paperNumber.positionProperty.value );

          // Set its destination to the proper target (with the offset so that it will disappear once centered).
          var targetPosition = this.explorePanel.getOriginLocation( baseNumber.digitLength );
          var paperCenterOffset = new PaperNumber( baseNumber.numberValue, new Vector2( 0, 0 ) ).getLocalBounds().center;
          targetPosition = targetPosition.minus( paperCenterOffset );
          basePaperNumber.setDestination( targetPosition, true );

          // Add the new base paper number
          this.model.addPaperNumber( basePaperNumber );
        }

        // Remove the original paper number (as we have added its components).
        this.model.removePaperNumber( paperNumber );
      }
    },

    /**
     * @override
     */
    reset: function() {
      MakeATenCommonView.prototype.reset.call( this );

      this.hideSumProperty.reset();
    }
  } );
} );