Skip to content

Commit

Permalink
simplify plates, see #142
Browse files Browse the repository at this point in the history
  • Loading branch information
jbphet committed Jan 25, 2024
1 parent 8c831a5 commit c85cbfa
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 70 deletions.
2 changes: 1 addition & 1 deletion js/common/MeanShareAndBalanceConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const MeanShareAndBalanceConstants = {
TABLE_PLATE_WIDTH: 100,

NOTEPAD_CANDY_BAR_CENTER_Y: 330,
PEOPLE_CENTER_Y: 500,
TABLE_PLATE_CENTER_Y: 500,

MAX_NUMBER_OF_CANDY_BARS_PER_PERSON: 10,
MIN_NUMBER_OF_CANDY_BARS: 0,
Expand Down
46 changes: 23 additions & 23 deletions js/leveling-out/model/LevelingOutModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import NumberProperty from '../../../../axon/js/NumberProperty.js';
import MeanShareAndBalanceConstants from '../../common/MeanShareAndBalanceConstants.js';
import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import NotepadPlate from './NotepadPlate.js';
import TablePlate from './TablePlate.js';
import Plate from './Plate.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
Expand All @@ -36,7 +36,7 @@ export default class LevelingOutModel extends MeanShareAndBalanceModel {
public readonly numberOfPeopleProperty: Property<number>;

public readonly notepadPlates: Array<NotepadPlate>;
public readonly tablePlates: Array<TablePlate>;
public readonly tablePlates: Array<Plate>;
public readonly candyBars: Array<CandyBar>;

public readonly meanProperty: TReadOnlyProperty<number>;
Expand Down Expand Up @@ -93,35 +93,35 @@ export default class LevelingOutModel extends MeanShareAndBalanceModel {

// Statically allocate plates, people, and candyBars. Whether they participate in the model is controlled by the
// isActiveProperty on each one.
for ( let tablePlateIndex = 0; tablePlateIndex < MAX_PEOPLE; tablePlateIndex++ ) {
const x = tablePlateIndex * MeanShareAndBalanceConstants.TABLE_PLATE_WIDTH;
for ( let plateIndex = 0; plateIndex < MAX_PEOPLE; plateIndex++ ) {
const x = plateIndex * MeanShareAndBalanceConstants.TABLE_PLATE_WIDTH;
const notepadPlate = new NotepadPlate( {
isActive: tablePlateIndex < this.numberOfPeopleProperty.value,
isActive: plateIndex < this.numberOfPeopleProperty.value,
position: new Vector2( x, MeanShareAndBalanceConstants.NOTEPAD_CANDY_BAR_CENTER_Y ),
linePlacement: tablePlateIndex,
linePlacement: plateIndex,

// phet-io
tandem: options.tandem.createTandem( `notepadPlate${tablePlateIndex + 1}` )
tandem: options.tandem.createTandem( `notepadPlate${plateIndex + 1}` )
} );
this.notepadPlates.push( notepadPlate );

const tablePlate = new TablePlate( {
position: new Vector2( x, MeanShareAndBalanceConstants.PEOPLE_CENTER_Y ),
isActive: tablePlateIndex < this.numberOfPeopleProperty.value,
linePlacement: tablePlateIndex,
const plate = new Plate( {
xPosition: x,
isActive: plateIndex < this.numberOfPeopleProperty.value,
linePlacement: plateIndex,

// phet-io
tandem: options.tandem.createTandem( `tablePlate${tablePlateIndex + 1}` )
tandem: options.tandem.createTandem( `plate${plateIndex + 1}` )
} );
this.tablePlates.push( tablePlate );
this.tablePlates.push( plate );

for ( let candyBarIndex = 0;
candyBarIndex < MeanShareAndBalanceConstants.MAX_NUMBER_OF_CANDY_BARS_PER_PERSON;
candyBarIndex++ ) {

const x = notepadPlate.position.x;
const y = notepadPlate.position.y - ( ( MeanShareAndBalanceConstants.CANDY_BAR_HEIGHT + 2 ) * ( candyBarIndex + 1 ) );
const isActive = notepadPlate.isActiveProperty.value && candyBarIndex < tablePlate.candyBarNumberProperty.value;
const isActive = notepadPlate.isActiveProperty.value && candyBarIndex < plate.snackNumberProperty.value;

const candyBar = new CandyBar( {
isActive: isActive,
Expand All @@ -142,7 +142,7 @@ export default class LevelingOutModel extends MeanShareAndBalanceModel {

// If a notepadPlate became inactive, we need to account for the extra or missing candy bar.
if ( !isActive ) {
const numberOfTablePlateCandyBars = this.tablePlates[ notepadPlate.linePlacement ].candyBarNumberProperty.value;
const numberOfTablePlateCandyBars = this.tablePlates[ notepadPlate.linePlacement ].snackNumberProperty.value;
if ( numberOfTablePlateCandyBars > numberOfCandyBarsOnPlate ) {
this.borrowMissingCandyBars( numberOfTablePlateCandyBars - numberOfCandyBarsOnPlate );
}
Expand All @@ -151,14 +151,14 @@ export default class LevelingOutModel extends MeanShareAndBalanceModel {
}
}
candyBars.forEach( ( candyBar, i ) => {
candyBar.isActiveProperty.value = isActive && i < tablePlate.candyBarNumberProperty.value;
candyBar.isActiveProperty.value = isActive && i < plate.snackNumberProperty.value;
this.reorganizeCandyBars( notepadPlate );
} );
} );

// Set paper notepadPlate candy bar number based on table notepadPlate delta change.
tablePlate.candyBarNumberProperty.lazyLink( ( candyBarNumber, oldCandyBarNumber ) => {
if ( tablePlate.isActiveProperty.value ) {
plate.snackNumberProperty.lazyLink( ( candyBarNumber, oldCandyBarNumber ) => {
if ( plate.isActiveProperty.value ) {
if ( candyBarNumber > oldCandyBarNumber ) {
this.tablePlateCandyBarAmountIncrease( notepadPlate, candyBarNumber - oldCandyBarNumber );
}
Expand All @@ -168,14 +168,14 @@ export default class LevelingOutModel extends MeanShareAndBalanceModel {
}
} );

totalCandyBarsPropertyDependencies.push( tablePlate.candyBarNumberProperty );
totalCandyBarsPropertyDependencies.push( tablePlate.isActiveProperty );
totalCandyBarsPropertyDependencies.push( plate.snackNumberProperty );
totalCandyBarsPropertyDependencies.push( plate.isActiveProperty );
}

// Tracks the total number of candyBars based on the "ground truth" tablePlate numbers.
// Must be deriveAny because .map() does not preserve .length()
this.totalCandyBarsProperty = DerivedProperty.deriveAny( totalCandyBarsPropertyDependencies, () => {
const candyBarAmounts = this.getActivePeople().map( tablePlate => tablePlate.candyBarNumberProperty.value );
const candyBarAmounts = this.getActivePeople().map( tablePlate => tablePlate.snackNumberProperty.value );
return _.sum( candyBarAmounts );
}, {
tandem: options.tandem.createTandem( 'totalCandyBarsProperty' ),
Expand All @@ -199,7 +199,7 @@ export default class LevelingOutModel extends MeanShareAndBalanceModel {
} );
}

public getActivePeople(): Array<TablePlate> {
public getActivePeople(): Array<Plate> {
return this.tablePlates.filter( tablePlate => tablePlate.isActiveProperty.value );
}

Expand Down Expand Up @@ -364,7 +364,7 @@ export default class LevelingOutModel extends MeanShareAndBalanceModel {

this.tablePlates.forEach( ( tablePlate, index ) => {
this.getCandyBarsOnPlate( this.notepadPlates[ index ] ).forEach( ( candyBar, i ) => {
candyBar.isActiveProperty.value = i < tablePlate.candyBarNumberProperty.value;
candyBar.isActiveProperty.value = i < tablePlate.snackNumberProperty.value;
} );
} );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,79 +1,75 @@
// Copyright 2022-2024, University of Colorado Boulder

/**
* The model representing the container for candy bars in the bottom representation.
* Tracks the position of a person and their notepadPlate, as well as how many candy bars they have brought
* The model representing the container for candy bars or cookies.
*
* @author Marla Schulz (PhET Interactive Simulations)
*
*/

import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import NumberProperty from '../../../../axon/js/NumberProperty.js';
import Property from '../../../../axon/js/Property.js';
import Range from '../../../../dot/js/Range.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import optionize from '../../../../phet-core/js/optionize.js';
import PickRequired from '../../../../phet-core/js/types/PickRequired.js';
import { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js';
import meanShareAndBalance from '../../meanShareAndBalance.js';

type SelfOptions = {
isActive: boolean;
position: Vector2;
xPosition: number;
linePlacement: number;
};

type TablePlateOptions = SelfOptions & PickRequired<PhetioObjectOptions, 'tandem'>;
type PlateOptions = SelfOptions & PickRequired<PhetioObjectOptions, 'tandem'>;

export default class TablePlate {
export default class Plate {

// Whether the cup is enabled in view and data calculations
public readonly isActiveProperty: Property<boolean>;

// The x and y positions for the person in the view. This specifies relative spacing between the people, and
// another container centers the group.
public readonly position: Vector2;
// The x position of the plate in view coordinates.
public readonly xPosition: number;

// The amount of candy bar bars the person brought
public readonly candyBarNumberProperty: Property<number>;
// The number of snacks (candy bars or cookies) on this plate.
public readonly snackNumberProperty: Property<number>;

// the person's index - 0-indexed
// The plate's index, 0-indexed. This is primarily used for debugging.
public readonly linePlacement: number;

public constructor( providedOptions?: TablePlateOptions ) {
public constructor( providedOptions: PlateOptions ) {

const options = optionize<TablePlateOptions, SelfOptions, PhetioObjectOptions>()( {}, providedOptions );
const options = optionize<PlateOptions, SelfOptions, PhetioObjectOptions>()( {}, providedOptions );

this.isActiveProperty = new BooleanProperty( options.isActive, {

// phet-io
tandem: options.tandem.createTandem( 'isActiveProperty' ),

// Takes its value from LevelingOutModel.numberOfPeopleProperty, so cannot be independently adjusted
// Takes its value from LevelingOutModel.numberOfPeopleProperty, so cannot be independently adjusted.
phetioReadOnly: true
} );
this.position = options.position;
this.xPosition = options.xPosition;

this.candyBarNumberProperty = new NumberProperty( options.isActive ? 1 : 0, {
this.snackNumberProperty = new NumberProperty( options.isActive ? 1 : 0, {

range: new Range( 0, 10 ),

// phet-io
tandem: options.tandem.createTandem( 'candyBarNumberProperty' )
tandem: options.tandem.createTandem( 'snackNumberProperty' )
} );

this.linePlacement = options.linePlacement;

// When the person becomes inactive, delete their candy bars. When a person becomes active, they arrive with 1 candy bar
this.isActiveProperty.lazyLink( isActive => this.candyBarNumberProperty.set( isActive ? 1 : 0 ) );
this.isActiveProperty.lazyLink( isActive => this.snackNumberProperty.set( isActive ? 1 : 0 ) );
}

// LinePlacement and position never changes and hence doesn't need to be reset.
public reset(): void {
this.isActiveProperty.reset();
this.candyBarNumberProperty.reset();
this.snackNumberProperty.reset();
}
}

meanShareAndBalance.register( 'TablePlate', TablePlate );
meanShareAndBalance.register( 'Plate', Plate );
18 changes: 9 additions & 9 deletions js/leveling-out/view/MeanCalculationDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Dialog from '../../../../sun/js/Dialog.js';
import meanShareAndBalance from '../../meanShareAndBalance.js';
import { GridBox, Line, Text, VBox } from '../../../../scenery/js/imports.js';
import Utils from '../../../../dot/js/Utils.js';
import TablePlate from '../model/TablePlate.js';
import Plate from '../model/Plate.js';
import Multilink from '../../../../axon/js/Multilink.js';
import MeanShareAndBalanceConstants from '../../common/MeanShareAndBalanceConstants.js';
import Property from '../../../../axon/js/Property.js';
Expand All @@ -22,7 +22,7 @@ import Tandem from '../../../../tandem/js/Tandem.js';

export default class MeanCalculationDialog extends Dialog {

public constructor( people: Array<TablePlate>, visibleProperty: Property<boolean>, notebookPaperBounds: Bounds2, tandem: Tandem ) {
public constructor( plates: Array<Plate>, visibleProperty: Property<boolean>, notebookPaperBounds: Bounds2, tandem: Tandem ) {

const meanTitleText = new Text( MeanShareAndBalanceStrings.meanStringProperty );
const meanEqualsAdditionFractionText = new Text( MeanShareAndBalanceStrings.meanEqualsStringProperty );
Expand All @@ -39,24 +39,24 @@ export default class MeanCalculationDialog extends Dialog {
minHeight: notebookPaperBounds.height - 40 // the top/bottom margin, and y spacing implemented by Dialog.ts
} );

const isActiveProperties = people.map( person => person.isActiveProperty );
const numberOfCandyBarsProperties = people.map( person => person.candyBarNumberProperty );
const isActiveProperties = plates.map( plate => plate.isActiveProperty );
const numberOfCandyBarsProperties = plates.map( plate => plate.snackNumberProperty );
Multilink.multilinkAny( [ ...isActiveProperties, ...numberOfCandyBarsProperties ], () => {
const numbers = people.filter( person => person.isActiveProperty.value ).map( person => person.candyBarNumberProperty.value );
const numberOfPeople = people.filter( person => person.isActiveProperty.value ).length;
const numbers = plates.filter( plate => plate.isActiveProperty.value ).map( plate => plate.snackNumberProperty.value );
const numberOfActivePlates = plates.filter( plate => plate.isActiveProperty.value ).length;

// REVIEW: Can we align the numbers with the table spinners? So correspondence is clear?
const additionText = new Text( numbers.join( ' + ' ) );
const additionFractionLine = new Line( 0, 0, additionText.width, 0, { stroke: 'black' } );
const additionDenominatorText = new Text( numberOfPeople );
const additionDenominatorText = new Text( numberOfActivePlates );
const additionFraction = new VBox( { children: [ additionText, additionFractionLine, additionDenominatorText ] } );

const numeratorText = new Text( _.sum( numbers ) );
const fractionLine = new Line( 0, 0, numeratorText.width, 0, { stroke: 'black' } );
const denominatorText = new Text( numberOfPeople );
const denominatorText = new Text( numberOfActivePlates );
const fraction = new VBox( { children: [ numeratorText, fractionLine, denominatorText ] } );

const decimalText = new Text( Utils.toFixedNumber( _.sum( numbers ) / numberOfPeople, 2 ) );
const decimalText = new Text( Utils.toFixedNumber( _.sum( numbers ) / numberOfActivePlates, 2 ) );

calculationNode.rows = [
[ meanEqualsAdditionFractionText, additionFraction ],
Expand Down
4 changes: 2 additions & 2 deletions js/leveling-out/view/NotepadPlateNode.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright 2022-2024, University of Colorado Boulder

/**
* In the upper (notepad) representation, contains all the candy bars on a notepadPlate. Each notepadPlate has one NotepadPlateNode,
* and each container has a maximum number of candy bars.
* In the upper (notepad) representation, contains all the candy bars on a notepadPlate. Each notepadPlate has one
* NotepadPlateNode, and each container has a maximum number of candy bars.
*
* @author Marla Schulz (PhET Interactive Simulations)
* @author Sam Reid (PhET Interactive Simulations)
Expand Down
34 changes: 21 additions & 13 deletions js/leveling-out/view/TablePlateNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import { Image, Node, NodeOptions, VBox } from '../../../../scenery/js/imports.js';
import meanShareAndBalance from '../../meanShareAndBalance.js';
import TablePlate from '../model/TablePlate.js';
import Plate from '../model/Plate.js';
import NumberPicker from '../../../../sun/js/NumberPicker.js';
import MeanShareAndBalanceConstants from '../../common/MeanShareAndBalanceConstants.js';
import Range from '../../../../dot/js/Range.js';
Expand All @@ -24,19 +24,27 @@ type PersonNodeOptions = PickRequired<NodeOptions, 'tandem'>;

export default class TablePlateNode extends Node {

public constructor( tablePlate: TablePlate, providedOptions: PersonNodeOptions ) {
public constructor( plate: Plate, providedOptions: PersonNodeOptions ) {

const options = providedOptions;

const plate = new Image( plate_png, {
const plateImage = new Image( plate_png, {
scale: 0.1,
centerY: tablePlate.position.y
centerY: MeanShareAndBalanceConstants.TABLE_PLATE_CENTER_Y
} );


const numberPickerRange = new Range( MeanShareAndBalanceConstants.MIN_NUMBER_OF_CANDY_BARS, MeanShareAndBalanceConstants.MAX_NUMBER_OF_CANDY_BARS_PER_PERSON );
const numberPicker = new NumberPicker( tablePlate.candyBarNumberProperty, new Property( numberPickerRange ),
{ centerTop: new Vector2( plate.centerBottom.x, plate.centerBottom.y + 55 ), tandem: options.tandem.createTandem( 'numberPicker' ) } );
const numberPickerRange = new Range(
MeanShareAndBalanceConstants.MIN_NUMBER_OF_CANDY_BARS,
MeanShareAndBalanceConstants.MAX_NUMBER_OF_CANDY_BARS_PER_PERSON
);
const numberPicker = new NumberPicker(
plate.snackNumberProperty,
new Property( numberPickerRange ),
{
centerTop: new Vector2( plateImage.centerBottom.x, plateImage.centerBottom.y + 55 ),
tandem: options.tandem.createTandem( 'numberPicker' )
}
);

const candyBarScale = 0.04;

Expand All @@ -51,24 +59,24 @@ export default class TablePlateNode extends Node {
spacing: 1.5
} );

tablePlate.candyBarNumberProperty.link( candyBarNumber => {
plate.snackNumberProperty.link( candyBarNumber => {
candyBars.forEach( ( chocolate, i ) => {
chocolate.visibleProperty.value = i < candyBarNumber;
candyBarsVBox.centerBottom = new Vector2( plate.centerX, plate.centerY );
candyBarsVBox.centerBottom = new Vector2( plateImage.centerX, plateImage.centerY );
} );
} );

const candyBarsNode = new Node( {
children: [ plate, candyBarsVBox ],
children: [ plateImage, candyBarsVBox ],
layoutOptions: {
minContentHeight: ( 265 * candyBarScale ) * 10
}
} );

super( {
children: [ candyBarsNode, numberPicker ],
x: tablePlate.position.x,
visibleProperty: tablePlate.isActiveProperty
x: plate.xPosition,
visibleProperty: plate.isActiveProperty
} );
}
}
Expand Down

0 comments on commit c85cbfa

Please sign in to comment.