From cbb00a866ee511b2c9ac2bcee56aa1032aa7acd7 Mon Sep 17 00:00:00 2001 From: samreid Date: Thu, 27 Feb 2020 11:39:59 -0700 Subject: [PATCH] ES6 module migration, see https://github.com/phetsims/chipper/issues/875 --- balancing-chemical-equations_en.html | 47 +- images/charts_png.js | 5 + js/balancing-chemical-equations-config.js | 51 -- js/balancing-chemical-equations-main.js | 48 +- js/balancing-chemical-equations-strings.js | 14 + js/balancingChemicalEquations.js | 8 +- js/common/BCEConstants.js | 31 +- js/common/BCEQueryParameters.js | 26 +- js/common/model/AtomCount.js | 145 +++-- js/common/model/BalancedRepresentation.js | 19 +- js/common/model/DecompositionEquation.js | 179 +++--- js/common/model/DisplacementEquation.js | 317 +++++----- js/common/model/Equation.js | 347 ++++++----- js/common/model/EquationTerm.js | 73 ++- js/common/model/Molecule.js | 67 +- js/common/model/MoleculeFactory.js | 345 ++++++----- js/common/model/SynthesisEquation.js | 168 +++--- js/common/view/BalanceScaleNode.js | 391 ++++++------ js/common/view/BalanceScalesNode.js | 221 ++++--- js/common/view/BarChartsNode.js | 265 ++++---- js/common/view/BarNode.js | 195 +++--- js/common/view/BeamNode.js | 61 +- js/common/view/BoxNode.js | 307 +++++----- js/common/view/BoxesNode.js | 141 +++-- js/common/view/EqualityOperatorNode.js | 95 ++- js/common/view/EquationNode.js | 286 +++++---- js/common/view/FulcrumNode.js | 89 ++- js/common/view/HorizontalAligner.js | 209 ++++--- js/common/view/RightArrowNode.js | 89 ++- js/common/view/TermNode.js | 113 ++-- js/game/GameScreen.js | 115 ++-- js/game/model/GameFactory.js | 371 ++++++------ js/game/model/GameModel.js | 461 +++++++------- js/game/model/RandomStrategy.js | 179 +++--- js/game/view/BCERewardNode.js | 229 ++++--- js/game/view/GameFeedbackDialog.js | 571 +++++++++--------- js/game/view/GamePlayNode.js | 455 +++++++------- js/game/view/GameScreenView.js | 259 ++++---- js/game/view/GameViewProperties.js | 37 +- js/game/view/LevelSelectionNode.js | 234 ++++--- js/introduction/IntroductionScreen.js | 201 +++--- js/introduction/model/IntroductionModel.js | 89 ++- js/introduction/view/EquationChoiceNode.js | 101 ++-- .../view/IntroductionScreenView.js | 241 ++++---- .../view/IntroductionViewProperties.js | 41 +- js/introduction/view/ToolsComboBox.js | 99 ++- mipmaps/scales_png.js | 38 ++ 47 files changed, 3989 insertions(+), 4084 deletions(-) create mode 100644 images/charts_png.js delete mode 100644 js/balancing-chemical-equations-config.js create mode 100644 js/balancing-chemical-equations-strings.js create mode 100644 mipmaps/scales_png.js diff --git a/balancing-chemical-equations_en.html b/balancing-chemical-equations_en.html index a8889294..6df2ec18 100644 --- a/balancing-chemical-equations_en.html +++ b/balancing-chemical-equations_en.html @@ -17,6 +17,35 @@ \ No newline at end of file diff --git a/images/charts_png.js b/images/charts_png.js new file mode 100644 index 00000000..c7e4b272 --- /dev/null +++ b/images/charts_png.js @@ -0,0 +1,5 @@ +/* eslint-disable */ +var img = new Image(); +window.phetImages.push( img ); +img.src = 'data:image/png;base64,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'; +export default img; diff --git a/js/balancing-chemical-equations-config.js b/js/balancing-chemical-equations-config.js deleted file mode 100644 index 1230d8af..00000000 --- a/js/balancing-chemical-equations-config.js +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2014-2019, University of Colorado Boulder - -/* - * IMPORTANT: This file was auto-generated by "grunt generate-config". Please do not modify this directly. Instead - * please modify balancing-chemical-equations/package.json to control dependencies. - * - * RequireJS configuration file for the balancing-chemical-equations sim. - * Paths are relative to the location of this file. - */ - -require.config( { - - deps: [ 'balancing-chemical-equations-main' ], - - paths: { - - // Third-party libs - text: '../../sherpa/lib/text-2.0.12', - - // PhET plugins - sound: '../../chipper/js/requirejs-plugins/sound', - image: '../../chipper/js/requirejs-plugins/image', - mipmap: '../../chipper/js/requirejs-plugins/mipmap', - string: '../../chipper/js/requirejs-plugins/string', - ifphetio: '../../chipper/js/requirejs-plugins/ifphetio', - - // PhET libs, uppercase names to identify them in require.js imports. - // IMPORTANT: DO NOT modify. This file is auto-generated. See documentation at the top. - AXON: '../../axon/js', - BALANCING_CHEMICAL_EQUATIONS: '.', - BRAND: '../../brand/' + phet.chipper.brand + '/js', - DOT: '../../dot/js', - JOIST: '../../joist/js', - KITE: '../../kite/js', - NITROGLYCERIN: '../../nitroglycerin/js', - PHETCOMMON: '../../phetcommon/js', - PHET_CORE: '../../phet-core/js', - PHET_IO: '../../phet-io/js', - REPOSITORY: '..', - SCENERY: '../../scenery/js', - SCENERY_PHET: '../../scenery-phet/js', - SUN: '../../sun/js', - TAMBO: '../../tambo/js', - TANDEM: '../../tandem/js', - UTTERANCE_QUEUE: '../../utterance-queue/js', - VEGAS: '../../vegas/js' - }, - - // Cache busting is applied by default, but can be disabled via ?cacheBust=false, see initialize-globals.js - urlArgs: phet.chipper.getCacheBustArgs() -} ); diff --git a/js/balancing-chemical-equations-main.js b/js/balancing-chemical-equations-main.js index 8df315af..4c409c4f 100644 --- a/js/balancing-chemical-equations-main.js +++ b/js/balancing-chemical-equations-main.js @@ -3,34 +3,30 @@ /** * Main entry point for the 'Balancing Chemical Equations' sim. */ -define( require => { - 'use strict'; - // modules - const GameScreen = require( 'BALANCING_CHEMICAL_EQUATIONS/game/GameScreen' ); - const IntroductionScreen = require( 'BALANCING_CHEMICAL_EQUATIONS/introduction/IntroductionScreen' ); - const Sim = require( 'JOIST/Sim' ); - const SimLauncher = require( 'JOIST/SimLauncher' ); +import Sim from '../../joist/js/Sim.js'; +import SimLauncher from '../../joist/js/SimLauncher.js'; +import balancingChemicalEquationsStrings from './balancing-chemical-equations-strings.js'; +import GameScreen from './game/GameScreen.js'; +import IntroductionScreen from './introduction/IntroductionScreen.js'; - // strings - const balancingChemicalEquationsTitleString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/balancing-chemical-equations.title' ); +const balancingChemicalEquationsTitleString = balancingChemicalEquationsStrings[ 'balancing-chemical-equations' ].title; - const simOptions = { - credits: { - leadDesign: 'Kelly Lancaster (Java), Yuen-ying Carpenter (HTML5)', - softwareDevelopment: 'Chris Malley (PixelZoom, Inc.)', - team: 'Julia Chamberlain, Patricia Loeblein, Emily B. Moore, Robert Parson, Ariel Paul, Kathy Perkins, Amy Rouinfar', - qualityAssurance: 'Steele Dalton, Bryce Griebenow, Elise Morgan, Liam Mulhall, Oliver Orejola, Laura Rae, Benjamin Roberts, Jacob Romero, Kathryn Woessner, Kelly Wurtz, Bryan Yoelin', - thanks: '\u2022 Conversion of this simulation to HTML5 was funded in part by the American Association of Chemistry Teachers (AACT).
' + - '\u2022 Thanks to Mobile Learner Labs for working with the PhET development team to convert this simulation to HTML5.' - } - }; +const simOptions = { + credits: { + leadDesign: 'Kelly Lancaster (Java), Yuen-ying Carpenter (HTML5)', + softwareDevelopment: 'Chris Malley (PixelZoom, Inc.)', + team: 'Julia Chamberlain, Patricia Loeblein, Emily B. Moore, Robert Parson, Ariel Paul, Kathy Perkins, Amy Rouinfar', + qualityAssurance: 'Steele Dalton, Bryce Griebenow, Elise Morgan, Liam Mulhall, Oliver Orejola, Laura Rae, Benjamin Roberts, Jacob Romero, Kathryn Woessner, Kelly Wurtz, Bryan Yoelin', + thanks: '\u2022 Conversion of this simulation to HTML5 was funded in part by the American Association of Chemistry Teachers (AACT).
' + + '\u2022 Thanks to Mobile Learner Labs for working with the PhET development team to convert this simulation to HTML5.' + } +}; - SimLauncher.launch( () => { - const screens = [ - new IntroductionScreen(), - new GameScreen() - ]; - new Sim( balancingChemicalEquationsTitleString, screens, simOptions ).start(); - } ); +SimLauncher.launch( () => { + const screens = [ + new IntroductionScreen(), + new GameScreen() + ]; + new Sim( balancingChemicalEquationsTitleString, screens, simOptions ).start(); } ); \ No newline at end of file diff --git a/js/balancing-chemical-equations-strings.js b/js/balancing-chemical-equations-strings.js new file mode 100644 index 00000000..1ddb63ea --- /dev/null +++ b/js/balancing-chemical-equations-strings.js @@ -0,0 +1,14 @@ +// Copyright 2020, University of Colorado Boulder + +/** + * Auto-generated from modulify, DO NOT manually modify. + */ + +import getStringModule from '../../chipper/js/getStringModule.js'; +import balancingChemicalEquations from './balancingChemicalEquations.js'; + +const balancingChemicalEquationsStrings = getStringModule( 'BALANCING_CHEMICAL_EQUATIONS' ); + +balancingChemicalEquations.register( 'balancingChemicalEquationsStrings', balancingChemicalEquationsStrings ); + +export default balancingChemicalEquationsStrings; diff --git a/js/balancingChemicalEquations.js b/js/balancingChemicalEquations.js index 2c4ac337..dc8beecc 100644 --- a/js/balancingChemicalEquations.js +++ b/js/balancingChemicalEquations.js @@ -5,11 +5,7 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const Namespace = require( 'PHET_CORE/Namespace' ); +import Namespace from '../../phet-core/js/Namespace.js'; - return new Namespace( 'balancingChemicalEquations' ); -} ); \ No newline at end of file +export default new Namespace( 'balancingChemicalEquations' ); \ No newline at end of file diff --git a/js/common/BCEConstants.js b/js/common/BCEConstants.js index 23f4f2a0..ffccd15d 100644 --- a/js/common/BCEConstants.js +++ b/js/common/BCEConstants.js @@ -6,23 +6,20 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const Bounds2 = require( 'DOT/Bounds2' ); +import Bounds2 from '../../../dot/js/Bounds2.js'; +import balancingChemicalEquations from '../balancingChemicalEquations.js'; - const BCEConstants = Object.freeze( { - SCREEN_VIEW_OPTIONS: { layoutBounds: new Bounds2( 0, 0, 768, 504 ) }, - UNBALANCED_COLOR: 'rgb(46,107,178)', - BALANCED_HIGHLIGHT_COLOR: 'yellow', - INTRODUCTION_CANVAS_BACKGROUND: '#d9ebff', - GAME_CANVAS_BACKGROUND: '#ffffe4', - BOX_COLOR: 'white', - ATOM_OPTIONS: { stroke: 'black', lineWidth: 0.5 }, - MOLECULE_SCALE_FACTOR: 0.74 // scale all molecules by scaleFactor to fit design - } ); +const BCEConstants = Object.freeze( { + SCREEN_VIEW_OPTIONS: { layoutBounds: new Bounds2( 0, 0, 768, 504 ) }, + UNBALANCED_COLOR: 'rgb(46,107,178)', + BALANCED_HIGHLIGHT_COLOR: 'yellow', + INTRODUCTION_CANVAS_BACKGROUND: '#d9ebff', + GAME_CANVAS_BACKGROUND: '#ffffe4', + BOX_COLOR: 'white', + ATOM_OPTIONS: { stroke: 'black', lineWidth: 0.5 }, + MOLECULE_SCALE_FACTOR: 0.74 // scale all molecules by scaleFactor to fit design +} ); - return balancingChemicalEquations.register( 'BCEConstants', BCEConstants ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'BCEConstants', BCEConstants ); +export default BCEConstants; \ No newline at end of file diff --git a/js/common/BCEQueryParameters.js b/js/common/BCEQueryParameters.js index 02b023e6..a23f2c73 100644 --- a/js/common/BCEQueryParameters.js +++ b/js/common/BCEQueryParameters.js @@ -5,25 +5,21 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); +import balancingChemicalEquations from '../balancingChemicalEquations.js'; - const BCEQueryParameters = QueryStringMachine.getAll( { +const BCEQueryParameters = QueryStringMachine.getAll( { - // play all challenges for each level of the game, to get 100% test coverage - playAll: { type: 'flag' }, + // play all challenges for each level of the game, to get 100% test coverage + playAll: { type: 'flag' }, - // show the game reward regardless of score - showReward: { type: 'flag' } - } ); + // show the game reward regardless of score + showReward: { type: 'flag' } +} ); - balancingChemicalEquations.register( 'BCEQueryParameters', BCEQueryParameters ); +balancingChemicalEquations.register( 'BCEQueryParameters', BCEQueryParameters ); - // log the values of all sim-specific query parameters - phet.log && phet.log( 'query parameters: ' + JSON.stringify( BCEQueryParameters, null, 2 ) ); +// log the values of all sim-specific query parameters +phet.log && phet.log( 'query parameters: ' + JSON.stringify( BCEQueryParameters, null, 2 ) ); - return BCEQueryParameters; -} ); +export default BCEQueryParameters; \ No newline at end of file diff --git a/js/common/model/AtomCount.js b/js/common/model/AtomCount.js index 3f8ad742..7bfc1cbc 100644 --- a/js/common/model/AtomCount.js +++ b/js/common/model/AtomCount.js @@ -8,94 +8,91 @@ * @author Vasily Shakhov * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; - class AtomCount { +class AtomCount { - /** - * @param {NITROGLYCERIN.Element} element - * @param {number} reactantsCount - * @param {number} productsCount - */ - constructor( element, reactantsCount, productsCount ) { - this.element = element; // @public the element that describes the atom's chemical properties - this.reactantsCount = reactantsCount; // @public - this.productsCount = productsCount; // @public - } - - /** - * Returns a count of each type of atom, based on the user coefficients. - * The order of atoms will be the same order that they are encountered in the terms, left to right. - * For example, if the equation is CH4 + 2 O2 -> CO2 + 2 H2O, then the order of atoms - * will be [C,H,O]. - * - * @param {Equation} equation - * @returns {AtomCount[]} - * @public - * @static - */ - static countAtoms( equation ) { - const atomCounts = []; - appendToCounts( atomCounts, equation.reactants, true /* isReactants */ ); - appendToCounts( atomCounts, equation.products, false /* isReactants */ ); - return atomCounts; - } + /** + * @param {NITROGLYCERIN.Element} element + * @param {number} reactantsCount + * @param {number} productsCount + */ + constructor( element, reactantsCount, productsCount ) { + this.element = element; // @public the element that describes the atom's chemical properties + this.reactantsCount = reactantsCount; // @public + this.productsCount = productsCount; // @public } /** - * Some of our visual representations of 'balanced' (ie, balance scales and bar charts) - * compare the number of atoms on the left and right side of the equation. - * - * This algorithm supports those representations by computing the atom counts. - * It examines a collection of terms in the equation (either reactants or products), - * examines those terms' molecules, and counts the number of each atom type. - * The atomCounts argument is modified, so that it contains the counts for the - * specified terms. - * - * This is a brute force algorithm, but our number of terms is always small, - * and this is easy to implement and understand. + * Returns a count of each type of atom, based on the user coefficients. + * The order of atoms will be the same order that they are encountered in the terms, left to right. + * For example, if the equation is CH4 + 2 O2 -> CO2 + 2 H2O, then the order of atoms + * will be [C,H,O]. * - * @param {AtomCount[]} atomCounts - * @param {EquationTerm[]} terms - * @param {boolean} isReactants true if the terms are the reactants, false if they are the products + * @param {Equation} equation + * @returns {AtomCount[]} + * @public + * @static */ - const appendToCounts = function( atomCounts, terms, isReactants ) { - terms.forEach( function( term ) { - term.molecule.atoms.forEach( function( atom ) { + static countAtoms( equation ) { + const atomCounts = []; + appendToCounts( atomCounts, equation.reactants, true /* isReactants */ ); + appendToCounts( atomCounts, equation.products, false /* isReactants */ ); + return atomCounts; + } +} - let found = false; - for ( let i = 0; i < atomCounts.length; i++ ) { - const atomCount = atomCounts[ i ]; - // add to an existing count - if ( atomCount.element === atom.element ) { - if ( isReactants ) { - atomCount.reactantsCount += term.userCoefficientProperty.get(); - } - else { - atomCount.productsCount += term.userCoefficientProperty.get(); - } - found = true; - break; - } - } +/** + * Some of our visual representations of 'balanced' (ie, balance scales and bar charts) + * compare the number of atoms on the left and right side of the equation. + * + * This algorithm supports those representations by computing the atom counts. + * It examines a collection of terms in the equation (either reactants or products), + * examines those terms' molecules, and counts the number of each atom type. + * The atomCounts argument is modified, so that it contains the counts for the + * specified terms. + * + * This is a brute force algorithm, but our number of terms is always small, + * and this is easy to implement and understand. + * + * @param {AtomCount[]} atomCounts + * @param {EquationTerm[]} terms + * @param {boolean} isReactants true if the terms are the reactants, false if they are the products + */ +const appendToCounts = function( atomCounts, terms, isReactants ) { + terms.forEach( function( term ) { + term.molecule.atoms.forEach( function( atom ) { - // if no existing count was found, create one. - if ( !found ) { + let found = false; + for ( let i = 0; i < atomCounts.length; i++ ) { + const atomCount = atomCounts[ i ]; + // add to an existing count + if ( atomCount.element === atom.element ) { if ( isReactants ) { - atomCounts.push( new AtomCount( atom.element, term.userCoefficientProperty.get(), 0 ) ); + atomCount.reactantsCount += term.userCoefficientProperty.get(); } else { - atomCounts.push( new AtomCount( atom.element, 0, term.userCoefficientProperty.get() ) ); + atomCount.productsCount += term.userCoefficientProperty.get(); } + found = true; + break; } - } ); + } + + // if no existing count was found, create one. + if ( !found ) { + if ( isReactants ) { + atomCounts.push( new AtomCount( atom.element, term.userCoefficientProperty.get(), 0 ) ); + } + else { + atomCounts.push( new AtomCount( atom.element, 0, term.userCoefficientProperty.get() ) ); + } + } } ); - return atomCounts; - }; + } ); + return atomCounts; +}; - return balancingChemicalEquations.register( 'AtomCount', AtomCount ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'AtomCount', AtomCount ); +export default AtomCount; \ No newline at end of file diff --git a/js/common/model/BalancedRepresentation.js b/js/common/model/BalancedRepresentation.js index f7caa21c..ff6d68cd 100644 --- a/js/common/model/BalancedRepresentation.js +++ b/js/common/model/BalancedRepresentation.js @@ -5,17 +5,14 @@ * * @author Vasily Shakhov (Mlearner) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; - const BalancedRepresentation = Object.freeze( { - 'NONE': 'NONE', - 'BALANCE_SCALES': 'BALANCE_SCALES', - 'BAR_CHARTS': 'BAR_CHARTS' - } ); - - return balancingChemicalEquations.register( 'BalancedRepresentation', BalancedRepresentation ); +const BalancedRepresentation = Object.freeze( { + 'NONE': 'NONE', + 'BALANCE_SCALES': 'BALANCE_SCALES', + 'BAR_CHARTS': 'BAR_CHARTS' } ); + +balancingChemicalEquations.register( 'BalancedRepresentation', BalancedRepresentation ); +export default BalancedRepresentation; \ No newline at end of file diff --git a/js/common/model/DecompositionEquation.js b/js/common/model/DecompositionEquation.js index a6d02b78..67047ad4 100644 --- a/js/common/model/DecompositionEquation.js +++ b/js/common/model/DecompositionEquation.js @@ -8,96 +8,93 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const Equation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/Equation' ); - const EquationTerm = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/EquationTerm' ); - const MoleculeFactory = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/MoleculeFactory' ); - - class DecompositionEquation extends Equation { - - /** - * @param {number} r1 balanced coefficient for reactant1 - * @param {Molecule} reactant1 - * @param {number} p1 balanced coefficient for product1 - * @param {Molecule} product1 - * @param {number} p2 balanced coefficient for product2 - * @param {Molecule} product2 - * @param {Object} [options] - */ - constructor( r1, reactant1, p1, product1, p2, product2, options ) { - super( - [ new EquationTerm( r1, reactant1 ) ], - [ new EquationTerm( p1, product1 ), new EquationTerm( p2, product2 ) ], - options - ); - } - - // @public @static - - // 2 H2O -> 2 H2 + O2 - static create_2H2O_2H2_O2() { - return new DecompositionEquation( 2, MoleculeFactory.H2O(), 2, MoleculeFactory.H2(), 1, MoleculeFactory.O2() ); - } - - // 2 HCl -> H2 + Cl2 - static create_2HCl_H2_Cl2() { - return new DecompositionEquation( 2, MoleculeFactory.HCl(), 1, MoleculeFactory.H2(), 1, MoleculeFactory.Cl2() ); - } - - // CH3OH -> CO + 2 H2 - static create_CH3OH_CO_2H2() { - return new DecompositionEquation( 1, MoleculeFactory.CH3OH(), 1, MoleculeFactory.CO(), 2, MoleculeFactory.H2() ); - } - - // C2H6 -> C2H4 + H2 - static create_C2H6_C2H4_H2() { - return new DecompositionEquation( 1, MoleculeFactory.C2H6(), 1, MoleculeFactory.C2H4(), 1, MoleculeFactory.H2() ); - } - - // 2 CO2 -> 2 CO + O2 - static create_2CO2_2CO_O2() { - return new DecompositionEquation( 2, MoleculeFactory.CO2(), 2, MoleculeFactory.CO(), 1, MoleculeFactory.O2() ); - } - - // 2 CO -> C + CO2 - static create_2CO_C_CO2() { - return new DecompositionEquation( 2, MoleculeFactory.CO(), 1, MoleculeFactory.C(), 1, MoleculeFactory.CO2() ); - } - - // 2 NH3 -> N2 + 3 H2 - static create_2NH3_N2_3H2() { - return new DecompositionEquation( 2, MoleculeFactory.NH3(), 1, MoleculeFactory.N2(), 3, MoleculeFactory.H2() ); - } - - // 2 NO -> N2 + O2 - static create_2NO_N2_O2() { - return new DecompositionEquation( 2, MoleculeFactory.NO(), 1, MoleculeFactory.N2(), 1, MoleculeFactory.O2() ); - } - - // 2 NO2 -> 2 NO + O2 - static create_2NO2_2NO_O2() { - return new DecompositionEquation( 2, MoleculeFactory.NO2(), 2, MoleculeFactory.NO(), 1, MoleculeFactory.O2() ); - } - - // 4 PCl3 -> P4 + 6 Cl2 - static create_4PCl3_P4_6Cl2() { - return new DecompositionEquation( 4, MoleculeFactory.PCl3(), 1, MoleculeFactory.P4(), 6, MoleculeFactory.Cl2() ); - } - - // PCl5 -> PCl3 + Cl2 - static create_PCl5_PCl3_Cl2() { - return new DecompositionEquation( 1, MoleculeFactory.PCl5(), 1, MoleculeFactory.PCl3(), 1, MoleculeFactory.Cl2() ); - } - - // 2 SO3 -> 2 SO2 + O2 - static create_2SO3_2SO2_O2() { - return new DecompositionEquation( 2, MoleculeFactory.SO3(), 2, MoleculeFactory.SO2(), 1, MoleculeFactory.O2() ); - } + +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import Equation from './Equation.js'; +import EquationTerm from './EquationTerm.js'; +import MoleculeFactory from './MoleculeFactory.js'; + +class DecompositionEquation extends Equation { + + /** + * @param {number} r1 balanced coefficient for reactant1 + * @param {Molecule} reactant1 + * @param {number} p1 balanced coefficient for product1 + * @param {Molecule} product1 + * @param {number} p2 balanced coefficient for product2 + * @param {Molecule} product2 + * @param {Object} [options] + */ + constructor( r1, reactant1, p1, product1, p2, product2, options ) { + super( + [ new EquationTerm( r1, reactant1 ) ], + [ new EquationTerm( p1, product1 ), new EquationTerm( p2, product2 ) ], + options + ); + } + + // @public @static + + // 2 H2O -> 2 H2 + O2 + static create_2H2O_2H2_O2() { + return new DecompositionEquation( 2, MoleculeFactory.H2O(), 2, MoleculeFactory.H2(), 1, MoleculeFactory.O2() ); + } + + // 2 HCl -> H2 + Cl2 + static create_2HCl_H2_Cl2() { + return new DecompositionEquation( 2, MoleculeFactory.HCl(), 1, MoleculeFactory.H2(), 1, MoleculeFactory.Cl2() ); + } + + // CH3OH -> CO + 2 H2 + static create_CH3OH_CO_2H2() { + return new DecompositionEquation( 1, MoleculeFactory.CH3OH(), 1, MoleculeFactory.CO(), 2, MoleculeFactory.H2() ); + } + + // C2H6 -> C2H4 + H2 + static create_C2H6_C2H4_H2() { + return new DecompositionEquation( 1, MoleculeFactory.C2H6(), 1, MoleculeFactory.C2H4(), 1, MoleculeFactory.H2() ); + } + + // 2 CO2 -> 2 CO + O2 + static create_2CO2_2CO_O2() { + return new DecompositionEquation( 2, MoleculeFactory.CO2(), 2, MoleculeFactory.CO(), 1, MoleculeFactory.O2() ); + } + + // 2 CO -> C + CO2 + static create_2CO_C_CO2() { + return new DecompositionEquation( 2, MoleculeFactory.CO(), 1, MoleculeFactory.C(), 1, MoleculeFactory.CO2() ); + } + + // 2 NH3 -> N2 + 3 H2 + static create_2NH3_N2_3H2() { + return new DecompositionEquation( 2, MoleculeFactory.NH3(), 1, MoleculeFactory.N2(), 3, MoleculeFactory.H2() ); + } + + // 2 NO -> N2 + O2 + static create_2NO_N2_O2() { + return new DecompositionEquation( 2, MoleculeFactory.NO(), 1, MoleculeFactory.N2(), 1, MoleculeFactory.O2() ); + } + + // 2 NO2 -> 2 NO + O2 + static create_2NO2_2NO_O2() { + return new DecompositionEquation( 2, MoleculeFactory.NO2(), 2, MoleculeFactory.NO(), 1, MoleculeFactory.O2() ); + } + + // 4 PCl3 -> P4 + 6 Cl2 + static create_4PCl3_P4_6Cl2() { + return new DecompositionEquation( 4, MoleculeFactory.PCl3(), 1, MoleculeFactory.P4(), 6, MoleculeFactory.Cl2() ); + } + + // PCl5 -> PCl3 + Cl2 + static create_PCl5_PCl3_Cl2() { + return new DecompositionEquation( 1, MoleculeFactory.PCl5(), 1, MoleculeFactory.PCl3(), 1, MoleculeFactory.Cl2() ); + } + + // 2 SO3 -> 2 SO2 + O2 + static create_2SO3_2SO2_O2() { + return new DecompositionEquation( 2, MoleculeFactory.SO3(), 2, MoleculeFactory.SO2(), 1, MoleculeFactory.O2() ); } +} - return balancingChemicalEquations.register( 'DecompositionEquation', DecompositionEquation ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'DecompositionEquation', DecompositionEquation ); +export default DecompositionEquation; \ No newline at end of file diff --git a/js/common/model/DisplacementEquation.js b/js/common/model/DisplacementEquation.js index 34587351..af9f502f 100644 --- a/js/common/model/DisplacementEquation.js +++ b/js/common/model/DisplacementEquation.js @@ -8,163 +8,160 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const Equation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/Equation' ); - const EquationTerm = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/EquationTerm' ); - const MoleculeFactory = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/MoleculeFactory' ); - - class DisplacementEquation extends Equation { - - /** - * @param {number} r1 balanced coefficient for reactant1 - * @param {Molecule} reactant1 - * @param {number} r2 balanced coefficient for reactant1 - * @param {Molecule} reactant2 - * @param {number} p1 balanced coefficient for product1 - * @param {Molecule} product1 - * @param {number} p2 balanced coefficient for product2 - * @param {Molecule} product2 - * @param {Object} [options] - */ - constructor( r1, reactant1, r2, reactant2, p1, product1, p2, product2, options ) { - super( - [ new EquationTerm( r1, reactant1 ), new EquationTerm( r2, reactant2 ) ], - [ new EquationTerm( p1, product1 ), new EquationTerm( p2, product2 ) ], - options - ); - } - - // @public @static - - // CH4 + 2 O2 -> CO2 + 2 H2O - static create_CH4_2O2_CO2_2H2O() { - return new DisplacementEquation( 1, MoleculeFactory.CH4(), 2, MoleculeFactory.O2(), 1, MoleculeFactory.CO2(), 2, MoleculeFactory.H2O() ); - } - - // 2 C + 2 H2O -> CH4 + CO2 - static create_2C_2H2O_CH4_CO2() { - return new DisplacementEquation( 2, MoleculeFactory.C(), 2, MoleculeFactory.H2O(), 1, MoleculeFactory.CH4(), 1, MoleculeFactory.CO2() ); - } - - // CH4 + H2O -> 3 H2 + CO - static create_CH4_H2O_3H2_CO() { - return new DisplacementEquation( 1, MoleculeFactory.CH4(), 1, MoleculeFactory.H2O(), 3, MoleculeFactory.H2(), 1, MoleculeFactory.CO() ); - } - - // C2H4 + 3 O2 -> 2 CO2 + 2 H2O - static create_C2H4_3O2_2CO2_2H2O() { - return new DisplacementEquation( 1, MoleculeFactory.C2H4(), 3, MoleculeFactory.O2(), 2, MoleculeFactory.CO2(), 2, MoleculeFactory.H2O() ); - } - - // C2H6 + Cl2 -> C2H5Cl + HCl - static create_C2H6_Cl2_C2H5Cl_HCl() { - return new DisplacementEquation( 1, MoleculeFactory.C2H6(), 1, MoleculeFactory.Cl2(), 1, MoleculeFactory.C2H5Cl(), 1, MoleculeFactory.HCl() ); - } - - // CH4 + 4 S -> CS2 + 2 H2S - static create_CH4_4S_CS2_2H2S() { - return new DisplacementEquation( 1, MoleculeFactory.CH4(), 4, MoleculeFactory.S(), 1, MoleculeFactory.CS2(), 2, MoleculeFactory.H2S() ); - } - - // CS2 + 3 O2 -> CO2 + 2 SO2 - static create_CS2_3O2_CO2_2SO2() { - return new DisplacementEquation( 1, MoleculeFactory.CS2(), 3, MoleculeFactory.O2(), 1, MoleculeFactory.CO2(), 2, MoleculeFactory.SO2() ); - } - - // SO2 + 2 H2 -> S + 2 H2O - static create_SO2_2H2_S_2H2O() { - return new DisplacementEquation( 1, MoleculeFactory.SO2(), 2, MoleculeFactory.H2(), 1, MoleculeFactory.S(), 2, MoleculeFactory.H2O() ); - } - - // SO2 + 3 H2 -> H2S + 2 H2O - static create_SO2_3H2_H2S_2H2O() { - return new DisplacementEquation( 1, MoleculeFactory.SO2(), 3, MoleculeFactory.H2(), 1, MoleculeFactory.H2S(), 2, MoleculeFactory.H2O() ); - } - - // 2 F2 + H2O -> OF2 + 2 HF - static create_2F2_H2O_OF2_2HF() { - return new DisplacementEquation( 2, MoleculeFactory.F2(), 1, MoleculeFactory.H2O(), 1, MoleculeFactory.OF2(), 2, MoleculeFactory.HF() ); - } - - // OF2 + H2O -> O2 + 2 HF - static create_OF2_H2O_O2_2HF() { - return new DisplacementEquation( 1, MoleculeFactory.OF2(), 1, MoleculeFactory.H2O(), 1, MoleculeFactory.O2(), 2, MoleculeFactory.HF() ); - } - - // 2 C2H6 + 7 O2 -> 4 CO2 + 6 H2O - static create_2C2H6_7O2_4CO2_6H2O() { - return new DisplacementEquation( 2, MoleculeFactory.C2H6(), 7, MoleculeFactory.O2(), 4, MoleculeFactory.CO2(), 6, MoleculeFactory.H2O() ); - } - - // 4 CO2 + 6 H2O -> 2 C2H6 + 7 O2 - static create_4CO2_6H2O_2C2H6_7O2() { - return new DisplacementEquation( 4, MoleculeFactory.CO2(), 6, MoleculeFactory.H2O(), 2, MoleculeFactory.C2H6(), 7, MoleculeFactory.O2() ); - } - - // 2 C2H2 + 5 O2 -> 4 CO2 + 2 H2O - static create_2C2H2_5O2_4CO2_2H2O() { - return new DisplacementEquation( 2, MoleculeFactory.C2H2(), 5, MoleculeFactory.O2(), 4, MoleculeFactory.CO2(), 2, MoleculeFactory.H2O() ); - } - - // 4 CO2 + 2 H2O -> 2 C2H2 + 5 O2 - static create_4CO2_2H2O_2C2H2_5O2() { - return new DisplacementEquation( 4, MoleculeFactory.CO2(), 2, MoleculeFactory.H2O(), 2, MoleculeFactory.C2H2(), 5, MoleculeFactory.O2() ); - } - - // C2H5OH + 3 O2 -> 2 CO2 + 3 H2O - static create_C2H5OH_3O2_2CO2_3H2O() { - return new DisplacementEquation( 1, MoleculeFactory.C2H5OH(), 3, MoleculeFactory.O2(), 2, MoleculeFactory.CO2(), 3, MoleculeFactory.H2O() ); - } - - // 2 CO2 + 3 H2O -> C2H5OH + 3 O2 - static create_2CO2_3H2O_C2H5OH_3O2() { - return new DisplacementEquation( 2, MoleculeFactory.CO2(), 3, MoleculeFactory.H2O(), 1, MoleculeFactory.C2H5OH(), 3, MoleculeFactory.O2() ); - } - - // 4 NH3 + 3 O2 -> 2 N2 + 6 H2O - static create_4NH3_3O2_2N2_6H2O() { - return new DisplacementEquation( 4, MoleculeFactory.NH3(), 3, MoleculeFactory.O2(), 2, MoleculeFactory.N2(), 6, MoleculeFactory.H2O() ); - } - - // 2 N2 + 6 H2O -> 4 NH3 + 3 O2 - static create_2N2_6H2O_4NH3_3O2() { - return new DisplacementEquation( 2, MoleculeFactory.N2(), 6, MoleculeFactory.H2O(), 4, MoleculeFactory.NH3(), 3, MoleculeFactory.O2() ); - } - - // 4 NH3 + 5 O2 -> 4 NO + 6 H2O - static create_4NH3_5O2_4NO_6H2O() { - return new DisplacementEquation( 4, MoleculeFactory.NH3(), 5, MoleculeFactory.O2(), 4, MoleculeFactory.NO(), 6, MoleculeFactory.H2O() ); - } - - // 4 NO + 6 H2O -> 4 NH3 + 5 O2 - static create_4NO_6H2O_4NH3_5O2() { - return new DisplacementEquation( 4, MoleculeFactory.NO(), 6, MoleculeFactory.H2O(), 4, MoleculeFactory.NH3(), 5, MoleculeFactory.O2() ); - } - - // 4 NH3 + 7 O2 -> 4 NO2 + 6 H2O - static create_4NH3_7O2_4NO2_6H2O() { - return new DisplacementEquation( 4, MoleculeFactory.NH3(), 7, MoleculeFactory.O2(), 4, MoleculeFactory.NO2(), 6, MoleculeFactory.H2O() ); - } - - // 4 NO2 + 6 H2O -> 4 NH3 + 7 O2 - static create_4NO2_6H2O_4NH3_7O2() { - return new DisplacementEquation( 4, MoleculeFactory.NO2(), 6, MoleculeFactory.H2O(), 4, MoleculeFactory.NH3(), 7, MoleculeFactory.O2() ); - } - - // 4 NH3 + 6 NO -> 5 N2 + 6 H2O - static create_4NH3_6NO_5N2_6H2O() { - return new DisplacementEquation( 4, MoleculeFactory.NH3(), 6, MoleculeFactory.NO(), 5, MoleculeFactory.N2(), 6, MoleculeFactory.H2O() ); - } - - // 5 N2 + 6 H2O -> 4 NH3 + 6 NO - static create_5N2_6H2O_4NH3_6NO() { - return new DisplacementEquation( 5, MoleculeFactory.N2(), 6, MoleculeFactory.H2O(), 4, MoleculeFactory.NH3(), 6, MoleculeFactory.NO() ); - } - } - - return balancingChemicalEquations.register( 'DisplacementEquation', DisplacementEquation ); -} ); \ No newline at end of file + +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import Equation from './Equation.js'; +import EquationTerm from './EquationTerm.js'; +import MoleculeFactory from './MoleculeFactory.js'; + +class DisplacementEquation extends Equation { + + /** + * @param {number} r1 balanced coefficient for reactant1 + * @param {Molecule} reactant1 + * @param {number} r2 balanced coefficient for reactant1 + * @param {Molecule} reactant2 + * @param {number} p1 balanced coefficient for product1 + * @param {Molecule} product1 + * @param {number} p2 balanced coefficient for product2 + * @param {Molecule} product2 + * @param {Object} [options] + */ + constructor( r1, reactant1, r2, reactant2, p1, product1, p2, product2, options ) { + super( + [ new EquationTerm( r1, reactant1 ), new EquationTerm( r2, reactant2 ) ], + [ new EquationTerm( p1, product1 ), new EquationTerm( p2, product2 ) ], + options + ); + } + + // @public @static + + // CH4 + 2 O2 -> CO2 + 2 H2O + static create_CH4_2O2_CO2_2H2O() { + return new DisplacementEquation( 1, MoleculeFactory.CH4(), 2, MoleculeFactory.O2(), 1, MoleculeFactory.CO2(), 2, MoleculeFactory.H2O() ); + } + + // 2 C + 2 H2O -> CH4 + CO2 + static create_2C_2H2O_CH4_CO2() { + return new DisplacementEquation( 2, MoleculeFactory.C(), 2, MoleculeFactory.H2O(), 1, MoleculeFactory.CH4(), 1, MoleculeFactory.CO2() ); + } + + // CH4 + H2O -> 3 H2 + CO + static create_CH4_H2O_3H2_CO() { + return new DisplacementEquation( 1, MoleculeFactory.CH4(), 1, MoleculeFactory.H2O(), 3, MoleculeFactory.H2(), 1, MoleculeFactory.CO() ); + } + + // C2H4 + 3 O2 -> 2 CO2 + 2 H2O + static create_C2H4_3O2_2CO2_2H2O() { + return new DisplacementEquation( 1, MoleculeFactory.C2H4(), 3, MoleculeFactory.O2(), 2, MoleculeFactory.CO2(), 2, MoleculeFactory.H2O() ); + } + + // C2H6 + Cl2 -> C2H5Cl + HCl + static create_C2H6_Cl2_C2H5Cl_HCl() { + return new DisplacementEquation( 1, MoleculeFactory.C2H6(), 1, MoleculeFactory.Cl2(), 1, MoleculeFactory.C2H5Cl(), 1, MoleculeFactory.HCl() ); + } + + // CH4 + 4 S -> CS2 + 2 H2S + static create_CH4_4S_CS2_2H2S() { + return new DisplacementEquation( 1, MoleculeFactory.CH4(), 4, MoleculeFactory.S(), 1, MoleculeFactory.CS2(), 2, MoleculeFactory.H2S() ); + } + + // CS2 + 3 O2 -> CO2 + 2 SO2 + static create_CS2_3O2_CO2_2SO2() { + return new DisplacementEquation( 1, MoleculeFactory.CS2(), 3, MoleculeFactory.O2(), 1, MoleculeFactory.CO2(), 2, MoleculeFactory.SO2() ); + } + + // SO2 + 2 H2 -> S + 2 H2O + static create_SO2_2H2_S_2H2O() { + return new DisplacementEquation( 1, MoleculeFactory.SO2(), 2, MoleculeFactory.H2(), 1, MoleculeFactory.S(), 2, MoleculeFactory.H2O() ); + } + + // SO2 + 3 H2 -> H2S + 2 H2O + static create_SO2_3H2_H2S_2H2O() { + return new DisplacementEquation( 1, MoleculeFactory.SO2(), 3, MoleculeFactory.H2(), 1, MoleculeFactory.H2S(), 2, MoleculeFactory.H2O() ); + } + + // 2 F2 + H2O -> OF2 + 2 HF + static create_2F2_H2O_OF2_2HF() { + return new DisplacementEquation( 2, MoleculeFactory.F2(), 1, MoleculeFactory.H2O(), 1, MoleculeFactory.OF2(), 2, MoleculeFactory.HF() ); + } + + // OF2 + H2O -> O2 + 2 HF + static create_OF2_H2O_O2_2HF() { + return new DisplacementEquation( 1, MoleculeFactory.OF2(), 1, MoleculeFactory.H2O(), 1, MoleculeFactory.O2(), 2, MoleculeFactory.HF() ); + } + + // 2 C2H6 + 7 O2 -> 4 CO2 + 6 H2O + static create_2C2H6_7O2_4CO2_6H2O() { + return new DisplacementEquation( 2, MoleculeFactory.C2H6(), 7, MoleculeFactory.O2(), 4, MoleculeFactory.CO2(), 6, MoleculeFactory.H2O() ); + } + + // 4 CO2 + 6 H2O -> 2 C2H6 + 7 O2 + static create_4CO2_6H2O_2C2H6_7O2() { + return new DisplacementEquation( 4, MoleculeFactory.CO2(), 6, MoleculeFactory.H2O(), 2, MoleculeFactory.C2H6(), 7, MoleculeFactory.O2() ); + } + + // 2 C2H2 + 5 O2 -> 4 CO2 + 2 H2O + static create_2C2H2_5O2_4CO2_2H2O() { + return new DisplacementEquation( 2, MoleculeFactory.C2H2(), 5, MoleculeFactory.O2(), 4, MoleculeFactory.CO2(), 2, MoleculeFactory.H2O() ); + } + + // 4 CO2 + 2 H2O -> 2 C2H2 + 5 O2 + static create_4CO2_2H2O_2C2H2_5O2() { + return new DisplacementEquation( 4, MoleculeFactory.CO2(), 2, MoleculeFactory.H2O(), 2, MoleculeFactory.C2H2(), 5, MoleculeFactory.O2() ); + } + + // C2H5OH + 3 O2 -> 2 CO2 + 3 H2O + static create_C2H5OH_3O2_2CO2_3H2O() { + return new DisplacementEquation( 1, MoleculeFactory.C2H5OH(), 3, MoleculeFactory.O2(), 2, MoleculeFactory.CO2(), 3, MoleculeFactory.H2O() ); + } + + // 2 CO2 + 3 H2O -> C2H5OH + 3 O2 + static create_2CO2_3H2O_C2H5OH_3O2() { + return new DisplacementEquation( 2, MoleculeFactory.CO2(), 3, MoleculeFactory.H2O(), 1, MoleculeFactory.C2H5OH(), 3, MoleculeFactory.O2() ); + } + + // 4 NH3 + 3 O2 -> 2 N2 + 6 H2O + static create_4NH3_3O2_2N2_6H2O() { + return new DisplacementEquation( 4, MoleculeFactory.NH3(), 3, MoleculeFactory.O2(), 2, MoleculeFactory.N2(), 6, MoleculeFactory.H2O() ); + } + + // 2 N2 + 6 H2O -> 4 NH3 + 3 O2 + static create_2N2_6H2O_4NH3_3O2() { + return new DisplacementEquation( 2, MoleculeFactory.N2(), 6, MoleculeFactory.H2O(), 4, MoleculeFactory.NH3(), 3, MoleculeFactory.O2() ); + } + + // 4 NH3 + 5 O2 -> 4 NO + 6 H2O + static create_4NH3_5O2_4NO_6H2O() { + return new DisplacementEquation( 4, MoleculeFactory.NH3(), 5, MoleculeFactory.O2(), 4, MoleculeFactory.NO(), 6, MoleculeFactory.H2O() ); + } + + // 4 NO + 6 H2O -> 4 NH3 + 5 O2 + static create_4NO_6H2O_4NH3_5O2() { + return new DisplacementEquation( 4, MoleculeFactory.NO(), 6, MoleculeFactory.H2O(), 4, MoleculeFactory.NH3(), 5, MoleculeFactory.O2() ); + } + + // 4 NH3 + 7 O2 -> 4 NO2 + 6 H2O + static create_4NH3_7O2_4NO2_6H2O() { + return new DisplacementEquation( 4, MoleculeFactory.NH3(), 7, MoleculeFactory.O2(), 4, MoleculeFactory.NO2(), 6, MoleculeFactory.H2O() ); + } + + // 4 NO2 + 6 H2O -> 4 NH3 + 7 O2 + static create_4NO2_6H2O_4NH3_7O2() { + return new DisplacementEquation( 4, MoleculeFactory.NO2(), 6, MoleculeFactory.H2O(), 4, MoleculeFactory.NH3(), 7, MoleculeFactory.O2() ); + } + + // 4 NH3 + 6 NO -> 5 N2 + 6 H2O + static create_4NH3_6NO_5N2_6H2O() { + return new DisplacementEquation( 4, MoleculeFactory.NH3(), 6, MoleculeFactory.NO(), 5, MoleculeFactory.N2(), 6, MoleculeFactory.H2O() ); + } + + // 5 N2 + 6 H2O -> 4 NH3 + 6 NO + static create_5N2_6H2O_4NH3_6NO() { + return new DisplacementEquation( 5, MoleculeFactory.N2(), 6, MoleculeFactory.H2O(), 4, MoleculeFactory.NH3(), 6, MoleculeFactory.NO() ); + } +} + +balancingChemicalEquations.register( 'DisplacementEquation', DisplacementEquation ); +export default DisplacementEquation; \ No newline at end of file diff --git a/js/common/model/Equation.js b/js/common/model/Equation.js index a2dbf9f9..e8970651 100644 --- a/js/common/model/Equation.js +++ b/js/common/model/Equation.js @@ -12,201 +12,198 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - - // modules - const AtomCount = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/AtomCount' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BooleanProperty = require( 'AXON/BooleanProperty' ); - const NumberProperty = require( 'AXON/NumberProperty' ); - - class Equation { - - /** - * @param {EquationTerm[]} reactants terms on the left side of the equation - * @param {EquationTerm[]} products terms on the right side of the equation - */ - constructor( reactants, products ) { - - // @public - this.reactants = reactants; - this.products = products; - - // @public - this.balancedProperty = new BooleanProperty( false ); - this.coefficientsSumProperty = new NumberProperty( 0, { numberType: 'Integer' } ); - - this.balancedAndSimplified = false; // @public balanced with the lowest possible coefficients - - this.addCoefficientsObserver( this.updateBalanced.bind( this ) ); - - // keep a sum of all coefficients, so we know when the sum is non-zero - this.addCoefficientsObserver( () => { - let coefficientsSum = 0; - const addCoefficients = equationTerm => { - coefficientsSum += equationTerm.userCoefficientProperty.get(); - }; - this.reactants.forEach( addCoefficients ); - this.products.forEach( addCoefficients ); - this.coefficientsSumProperty.set( coefficientsSum ); - } ); - } - //TODO https://github.com/phetsims/balancing-chemical-equations/issues/142 is dispose needed? +import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; +import NumberProperty from '../../../../axon/js/NumberProperty.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import AtomCount from './AtomCount.js'; + +class Equation { + + /** + * @param {EquationTerm[]} reactants terms on the left side of the equation + * @param {EquationTerm[]} products terms on the right side of the equation + */ + constructor( reactants, products ) { // @public - reset() { + this.reactants = reactants; + this.products = products; - this.balancedProperty.reset(); - this.coefficientsSumProperty.reset(); + // @public + this.balancedProperty = new BooleanProperty( false ); + this.coefficientsSumProperty = new NumberProperty( 0, { numberType: 'Integer' } ); + + this.balancedAndSimplified = false; // @public balanced with the lowest possible coefficients + + this.addCoefficientsObserver( this.updateBalanced.bind( this ) ); + + // keep a sum of all coefficients, so we know when the sum is non-zero + this.addCoefficientsObserver( () => { + let coefficientsSum = 0; + const addCoefficients = equationTerm => { + coefficientsSum += equationTerm.userCoefficientProperty.get(); + }; + this.reactants.forEach( addCoefficients ); + this.products.forEach( addCoefficients ); + this.coefficientsSumProperty.set( coefficientsSum ); + } ); + } - this.reactants.forEach( reactant => reactant.reset() ); - this.products.forEach( product => product.reset() ); - } + //TODO https://github.com/phetsims/balancing-chemical-equations/issues/142 is dispose needed? - /** - * An equation is balanced if all of its terms have a coefficient that is the - * same integer multiple of the term's balanced coefficient. If the integer - * multiple is 1, then the term is "balanced and simplified" (balanced with - * lowest possible coefficients). - * @private - */ - updateBalanced() { - - // Get integer multiplier from the first reactant term. - const multiplier = this.reactants[ 0 ].userCoefficientProperty.get() / this.reactants[ 0 ].balancedCoefficient; - let balanced = ( multiplier > 0 ); - - // Check each term to see if the actual coefficient is the same integer multiple of the balanced coefficient. - this.reactants.forEach( reactant => { - balanced = balanced && ( reactant.userCoefficientProperty.get() === multiplier * reactant.balancedCoefficient ); - } ); - this.products.forEach( product => { - balanced = balanced && ( product.userCoefficientProperty.get() === multiplier * product.balancedCoefficient ); - } ); - - this.balancedAndSimplified = balanced && ( multiplier === 1 ); // set the more specific value first - this.balancedProperty.set( balanced ); - } + // @public + reset() { - /** - * Convenience method for adding an observer to all coefficients. - * @public - */ - addCoefficientsObserver( observer ) { - this.reactants.forEach( reactant => reactant.userCoefficientProperty.lazyLink( observer ) ); - this.products.forEach( product => product.userCoefficientProperty.lazyLink( observer ) ); - observer(); - } + this.balancedProperty.reset(); + this.coefficientsSumProperty.reset(); - /** - * Convenience method for removing an observer from all coefficients. - * @public - */ - removeCoefficientsObserver( observer ) { - this.reactants.forEach( reactant => reactant.userCoefficientProperty.unlink( observer ) ); - this.products.forEach( product => product.userCoefficientProperty.unlink( observer ) ); - } + this.reactants.forEach( reactant => reactant.reset() ); + this.products.forEach( product => product.reset() ); + } - /** - * Returns a count of each type of atom, based on the user coefficients. - * See AtomCount.countAtoms for details. - * @returns {AtomCount[]} - * @public - */ - getAtomCounts() { - return AtomCount.countAtoms( this ); - } + /** + * An equation is balanced if all of its terms have a coefficient that is the + * same integer multiple of the term's balanced coefficient. If the integer + * multiple is 1, then the term is "balanced and simplified" (balanced with + * lowest possible coefficients). + * @private + */ + updateBalanced() { + + // Get integer multiplier from the first reactant term. + const multiplier = this.reactants[ 0 ].userCoefficientProperty.get() / this.reactants[ 0 ].balancedCoefficient; + let balanced = ( multiplier > 0 ); + + // Check each term to see if the actual coefficient is the same integer multiple of the balanced coefficient. + this.reactants.forEach( reactant => { + balanced = balanced && ( reactant.userCoefficientProperty.get() === multiplier * reactant.balancedCoefficient ); + } ); + this.products.forEach( product => { + balanced = balanced && ( product.userCoefficientProperty.get() === multiplier * product.balancedCoefficient ); + } ); + + this.balancedAndSimplified = balanced && ( multiplier === 1 ); // set the more specific value first + this.balancedProperty.set( balanced ); + } - /** - * Does this equation contain at least one "big" molecule? - * This affects degree of difficulty in the Game. - * @returns {boolean} - * @public - */ - hasBigMolecule() { - this.reactants.forEach( reactant => { - if ( reactant.molecule.isBig() ) { - return true; - } - } ); - this.products.forEach( product => { - if ( product.molecule.isBig() ) { - return true; - } - } ); - return false; - } + /** + * Convenience method for adding an observer to all coefficients. + * @public + */ + addCoefficientsObserver( observer ) { + this.reactants.forEach( reactant => reactant.userCoefficientProperty.lazyLink( observer ) ); + this.products.forEach( product => product.userCoefficientProperty.lazyLink( observer ) ); + observer(); + } - /** - * Balances the equation by copying the balanced coefficient value to - * the user coefficient value for each term in the equation. - * @public - */ - balance() { - this.reactants.forEach( term => term.userCoefficientProperty.set( term.balancedCoefficient ) ); - this.products.forEach( term => term.userCoefficientProperty.set( term.balancedCoefficient ) ); - } + /** + * Convenience method for removing an observer from all coefficients. + * @public + */ + removeCoefficientsObserver( observer ) { + this.reactants.forEach( reactant => reactant.userCoefficientProperty.unlink( observer ) ); + this.products.forEach( product => product.userCoefficientProperty.unlink( observer ) ); + } + + /** + * Returns a count of each type of atom, based on the user coefficients. + * See AtomCount.countAtoms for details. + * @returns {AtomCount[]} + * @public + */ + getAtomCounts() { + return AtomCount.countAtoms( this ); + } - /** - * Gets a string that shows just the coefficients of the equations. - * This is used to show game answers when running in 'dev' mode. - * @returns {string} - * @public - */ - getCoefficientsString() { - let string = ''; - for ( var i = 0; i < this.reactants.length; i++ ) { - string += this.reactants[ i ].balancedCoefficient; - string += ( i < this.reactants.length - 1 ) ? ' + ' : ' '; + /** + * Does this equation contain at least one "big" molecule? + * This affects degree of difficulty in the Game. + * @returns {boolean} + * @public + */ + hasBigMolecule() { + this.reactants.forEach( reactant => { + if ( reactant.molecule.isBig() ) { + return true; } - string += '\u2192 '; // right arrow - for ( i = 0; i < this.products.length; i++ ) { - string += this.products[ i ].balancedCoefficient; - string += ( i < this.products.length - 1 ) ? ' + ' : ''; + } ); + this.products.forEach( product => { + if ( product.molecule.isBig() ) { + return true; } - return string; + } ); + return false; + } + + /** + * Balances the equation by copying the balanced coefficient value to + * the user coefficient value for each term in the equation. + * @public + */ + balance() { + this.reactants.forEach( term => term.userCoefficientProperty.set( term.balancedCoefficient ) ); + this.products.forEach( term => term.userCoefficientProperty.set( term.balancedCoefficient ) ); + } + + /** + * Gets a string that shows just the coefficients of the equations. + * This is used to show game answers when running in 'dev' mode. + * @returns {string} + * @public + */ + getCoefficientsString() { + let string = ''; + for ( var i = 0; i < this.reactants.length; i++ ) { + string += this.reactants[ i ].balancedCoefficient; + string += ( i < this.reactants.length - 1 ) ? ' + ' : ' '; + } + string += '\u2192 '; // right arrow + for ( i = 0; i < this.products.length; i++ ) { + string += this.products[ i ].balancedCoefficient; + string += ( i < this.products.length - 1 ) ? ' + ' : ''; } + return string; + } - /** - * String value of an equation, shows balanced coefficients, for debugging. - * @returns {string} - * @public - */ - toString() { - let string = ''; - - // reactants - for ( var i = 0; i < this.reactants.length; i++ ) { - string += this.reactants[ i ].balancedCoefficient; - string += ' '; - string += this.reactants[ i ].molecule.symbol; - if ( i < this.reactants.length - 1 ) { - string += ' + '; - } + /** + * String value of an equation, shows balanced coefficients, for debugging. + * @returns {string} + * @public + */ + toString() { + let string = ''; + + // reactants + for ( var i = 0; i < this.reactants.length; i++ ) { + string += this.reactants[ i ].balancedCoefficient; + string += ' '; + string += this.reactants[ i ].molecule.symbol; + if ( i < this.reactants.length - 1 ) { + string += ' + '; } + } - // right arrow - string += ' \u2192 '; - - // products - for ( i = 0; i < this.products.length; i++ ) { - string += this.products[ i ].balancedCoefficient; - string += ' '; - string += this.products[ i ].molecule.symbol; - if ( i < this.products.length - 1 ) { - string += ' + '; - } + // right arrow + string += ' \u2192 '; + + // products + for ( i = 0; i < this.products.length; i++ ) { + string += this.products[ i ].balancedCoefficient; + string += ' '; + string += this.products[ i ].molecule.symbol; + if ( i < this.products.length - 1 ) { + string += ' + '; } + } - // strip out HTML tags to improve readability - string = string.replace( //g, '' ); - string = string.replace( /<\/sub>/g, '' ); + // strip out HTML tags to improve readability + string = string.replace( //g, '' ); + string = string.replace( /<\/sub>/g, '' ); - return string; - } + return string; } +} - return balancingChemicalEquations.register( 'Equation', Equation ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'Equation', Equation ); +export default Equation; \ No newline at end of file diff --git a/js/common/model/EquationTerm.js b/js/common/model/EquationTerm.js index 69f86113..d88c192a 100644 --- a/js/common/model/EquationTerm.js +++ b/js/common/model/EquationTerm.js @@ -7,45 +7,42 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEQueryParameters = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEQueryParameters' ); - const merge = require( 'PHET_CORE/merge' ); - const NumberProperty = require( 'AXON/NumberProperty' ); - - class EquationTerm { - - /** - * @param {number} balancedCoefficient balanced coefficient for molecule - * @param {Molecule} molecule - * @param {Object} [options] - */ - constructor( balancedCoefficient, molecule, options ) { - - options = merge( { - initialCoefficient: 0 // initial value of the coefficient - }, options ); - - // If we're inspecting all game challenges, fill in the correct answer to make our job easier. - if ( BCEQueryParameters.playAll ) { - options.initialCoefficient = balancedCoefficient; - } - - this.molecule = molecule; // @public - this.balancedCoefficient = balancedCoefficient; // @public - this.userCoefficientProperty = new NumberProperty( options.initialCoefficient, { - numberType: 'Integer' - } ); // @public - } - // @public - reset() { - this.userCoefficientProperty.reset(); +import NumberProperty from '../../../../axon/js/NumberProperty.js'; +import merge from '../../../../phet-core/js/merge.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEQueryParameters from '../BCEQueryParameters.js'; + +class EquationTerm { + + /** + * @param {number} balancedCoefficient balanced coefficient for molecule + * @param {Molecule} molecule + * @param {Object} [options] + */ + constructor( balancedCoefficient, molecule, options ) { + + options = merge( { + initialCoefficient: 0 // initial value of the coefficient + }, options ); + + // If we're inspecting all game challenges, fill in the correct answer to make our job easier. + if ( BCEQueryParameters.playAll ) { + options.initialCoefficient = balancedCoefficient; } + + this.molecule = molecule; // @public + this.balancedCoefficient = balancedCoefficient; // @public + this.userCoefficientProperty = new NumberProperty( options.initialCoefficient, { + numberType: 'Integer' + } ); // @public + } + + // @public + reset() { + this.userCoefficientProperty.reset(); } +} - return balancingChemicalEquations.register( 'EquationTerm', EquationTerm ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'EquationTerm', EquationTerm ); +export default EquationTerm; \ No newline at end of file diff --git a/js/common/model/Molecule.js b/js/common/model/Molecule.js index d833e6eb..aa38f8cd 100644 --- a/js/common/model/Molecule.js +++ b/js/common/model/Molecule.js @@ -5,40 +5,37 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const Atom = require( 'NITROGLYCERIN/Atom' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - - class Molecule { - - /** - * @param {NITROGLYCERIN.node} nodeConstructor constructor of molecule from NITROGLYCERIN - * @param {string} symbolText html string - * @param {NITROGLYCERIN.Element[]} elements - */ - constructor( nodeConstructor, symbolText, elements ) { - - // @public - this.nodeConstructor = nodeConstructor; - this.symbol = symbolText; - this.atoms = []; - - elements.forEach( element => this.atoms.push( new Atom( element ) ) ); - } - - /** - * Any molecule with more than 5 atoms is considered "big". - * This affects degree of difficulty in the Game. - * @returns {boolean} - * @public - */ - isBig() { - return this.atoms.length > 5; - } + +import Atom from '../../../../nitroglycerin/js/Atom.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; + +class Molecule { + + /** + * @param {NITROGLYCERIN.node} nodeConstructor constructor of molecule from NITROGLYCERIN + * @param {string} symbolText html string + * @param {NITROGLYCERIN.Element[]} elements + */ + constructor( nodeConstructor, symbolText, elements ) { + + // @public + this.nodeConstructor = nodeConstructor; + this.symbol = symbolText; + this.atoms = []; + + elements.forEach( element => this.atoms.push( new Atom( element ) ) ); + } + + /** + * Any molecule with more than 5 atoms is considered "big". + * This affects degree of difficulty in the Game. + * @returns {boolean} + * @public + */ + isBig() { + return this.atoms.length > 5; } +} - return balancingChemicalEquations.register( 'Molecule', Molecule ); -} ); +balancingChemicalEquations.register( 'Molecule', Molecule ); +export default Molecule; \ No newline at end of file diff --git a/js/common/model/MoleculeFactory.js b/js/common/model/MoleculeFactory.js index 2a05f703..040aa060 100644 --- a/js/common/model/MoleculeFactory.js +++ b/js/common/model/MoleculeFactory.js @@ -5,188 +5,185 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const Element = require( 'NITROGLYCERIN/Element' ); - const Molecule = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/Molecule' ); - - // modules - molecules - const C2H2Node = require( 'NITROGLYCERIN/nodes/C2H2Node' ); - const C2H4Node = require( 'NITROGLYCERIN/nodes/C2H4Node' ); - const C2H5ClNode = require( 'NITROGLYCERIN/nodes/C2H5ClNode' ); - const C2H5OHNode = require( 'NITROGLYCERIN/nodes/C2H5OHNode' ); - const C2H6Node = require( 'NITROGLYCERIN/nodes/C2H6Node' ); - const CH2ONode = require( 'NITROGLYCERIN/nodes/CH2ONode' ); - const CH3OHNode = require( 'NITROGLYCERIN/nodes/CH3OHNode' ); - const CH4Node = require( 'NITROGLYCERIN/nodes/CH4Node' ); - const Cl2Node = require( 'NITROGLYCERIN/nodes/Cl2Node' ); - const CNode = require( 'NITROGLYCERIN/nodes/CNode' ); - const CO2Node = require( 'NITROGLYCERIN/nodes/CO2Node' ); - const CONode = require( 'NITROGLYCERIN/nodes/CONode' ); - const CS2Node = require( 'NITROGLYCERIN/nodes/CS2Node' ); - const F2Node = require( 'NITROGLYCERIN/nodes/F2Node' ); - const H2Node = require( 'NITROGLYCERIN/nodes/H2Node' ); - const H2ONode = require( 'NITROGLYCERIN/nodes/H2ONode' ); - const H2SNode = require( 'NITROGLYCERIN/nodes/H2SNode' ); - const HClNode = require( 'NITROGLYCERIN/nodes/HClNode' ); - const HFNode = require( 'NITROGLYCERIN/nodes/HFNode' ); - const N2Node = require( 'NITROGLYCERIN/nodes/N2Node' ); - const N2ONode = require( 'NITROGLYCERIN/nodes/N2ONode' ); - const NH3Node = require( 'NITROGLYCERIN/nodes/NH3Node' ); - const NO2Node = require( 'NITROGLYCERIN/nodes/NO2Node' ); - const NONode = require( 'NITROGLYCERIN/nodes/NONode' ); - const O2Node = require( 'NITROGLYCERIN/nodes/O2Node' ); - const OF2Node = require( 'NITROGLYCERIN/nodes/OF2Node' ); - const P4Node = require( 'NITROGLYCERIN/nodes/P4Node' ); - const PCl3Node = require( 'NITROGLYCERIN/nodes/PCl3Node' ); - const PCl5Node = require( 'NITROGLYCERIN/nodes/PCl5Node' ); - const PF3Node = require( 'NITROGLYCERIN/nodes/PF3Node' ); - const PH3Node = require( 'NITROGLYCERIN/nodes/PH3Node' ); - const SNode = require( 'NITROGLYCERIN/nodes/SNode' ); - const SO2Node = require( 'NITROGLYCERIN/nodes/SO2Node' ); - const SO3Node = require( 'NITROGLYCERIN/nodes/SO3Node' ); - - const MoleculeFactory = { - - C: function() { - return new Molecule( CNode, 'C', [ Element.C ] ); - }, - - Cl2: function() { - return new Molecule( Cl2Node, 'Cl2', [ Element.Cl, Element.Cl ] ); - }, - - C2H2: function() { - return new Molecule( C2H2Node, 'C2H2', [ Element.C, Element.C, Element.H, Element.H ] ); - }, - - C2H4: function() { - return new Molecule( C2H4Node, 'C2H4', [ Element.C, Element.C, Element.H, Element.H, Element.H, Element.H ] ); - }, - - C2H5Cl: function() { - return new Molecule( C2H5ClNode, 'C2H5Cl', [ Element.C, Element.C, Element.H, Element.H, Element.H, Element.H, Element.H, Element.Cl ] ); - }, - - C2H5OH: function() { - return new Molecule( C2H5OHNode, 'C2H5OH', [ Element.C, Element.C, Element.H, Element.H, Element.H, Element.H, Element.H, Element.O, Element.H ] ); - }, - - C2H6: function() { - return new Molecule( C2H6Node, 'C2H6', [ Element.C, Element.C, Element.H, Element.H, Element.H, Element.H, Element.H, Element.H ] ); - }, - - CH2O: function() { - return new Molecule( CH2ONode, 'CH2O', [ Element.C, Element.H, Element.H, Element.O ] ); - }, - - CH3OH: function() { - return new Molecule( CH3OHNode, 'CH3OH', [ Element.C, Element.H, Element.H, Element.H, Element.O, Element.H ] ); - }, - - CH4: function() { - return new Molecule( CH4Node, 'CH4', [ Element.C, Element.H, Element.H, Element.H, Element.H ] ); - }, - - CO: function() { - return new Molecule( CONode, 'CO', [ Element.C, Element.O ] ); - }, - - CO2: function() { - return new Molecule( CO2Node, 'CO2', [ Element.C, Element.O, Element.O ] ); - }, - - CS2: function() { - return new Molecule( CS2Node, 'CS2', [ Element.C, Element.S, Element.S ] ); - }, - - F2: function() { - return new Molecule( F2Node, 'F2', [ Element.F, Element.F ] ); - }, - - H2: function() { - return new Molecule( H2Node, 'H2', [ Element.H, Element.H ] ); - }, - - H2O: function() { - return new Molecule( H2ONode, 'H2O', [ Element.H, Element.H, Element.O ] ); - }, - - H2S: function() { - return new Molecule( H2SNode, 'H2S', [ Element.H, Element.H, Element.S ] ); - }, - - HF: function() { - return new Molecule( HFNode, 'HF', [ Element.H, Element.F ] ); - }, - - HCl: function() { - return new Molecule( HClNode, 'HCl', [ Element.H, Element.Cl ] ); - }, - - N2: function() { - return new Molecule( N2Node, 'N2', [ Element.N, Element.N ] ); - }, - - N2O: function() { - return new Molecule( N2ONode, 'N2O', [ Element.N, Element.N, Element.O ] ); - }, - - NH3: function() { - return new Molecule( NH3Node, 'NH3', [ Element.N, Element.H, Element.H, Element.H ] ); - }, - - NO: function() { - return new Molecule( NONode, 'NO', [ Element.N, Element.O ] ); - }, - - NO2: function() { - return new Molecule( NO2Node, 'NO2', [ Element.N, Element.O, Element.O ] ); - }, - O2: function() { - return new Molecule( O2Node, 'O2', [ Element.O, Element.O ] ); - }, +import Element from '../../../../nitroglycerin/js/Element.js'; +import C2H2Node from '../../../../nitroglycerin/js/nodes/C2H2Node.js'; +import C2H4Node from '../../../../nitroglycerin/js/nodes/C2H4Node.js'; +import C2H5ClNode from '../../../../nitroglycerin/js/nodes/C2H5ClNode.js'; +import C2H5OHNode from '../../../../nitroglycerin/js/nodes/C2H5OHNode.js'; +import C2H6Node from '../../../../nitroglycerin/js/nodes/C2H6Node.js'; +import CH2ONode from '../../../../nitroglycerin/js/nodes/CH2ONode.js'; +import CH3OHNode from '../../../../nitroglycerin/js/nodes/CH3OHNode.js'; +import CH4Node from '../../../../nitroglycerin/js/nodes/CH4Node.js'; +import Cl2Node from '../../../../nitroglycerin/js/nodes/Cl2Node.js'; +import CNode from '../../../../nitroglycerin/js/nodes/CNode.js'; +import CO2Node from '../../../../nitroglycerin/js/nodes/CO2Node.js'; +import CONode from '../../../../nitroglycerin/js/nodes/CONode.js'; +import CS2Node from '../../../../nitroglycerin/js/nodes/CS2Node.js'; +import F2Node from '../../../../nitroglycerin/js/nodes/F2Node.js'; +import H2Node from '../../../../nitroglycerin/js/nodes/H2Node.js'; +import H2ONode from '../../../../nitroglycerin/js/nodes/H2ONode.js'; +import H2SNode from '../../../../nitroglycerin/js/nodes/H2SNode.js'; +import HClNode from '../../../../nitroglycerin/js/nodes/HClNode.js'; +import HFNode from '../../../../nitroglycerin/js/nodes/HFNode.js'; +import N2Node from '../../../../nitroglycerin/js/nodes/N2Node.js'; +import N2ONode from '../../../../nitroglycerin/js/nodes/N2ONode.js'; +import NH3Node from '../../../../nitroglycerin/js/nodes/NH3Node.js'; +import NO2Node from '../../../../nitroglycerin/js/nodes/NO2Node.js'; +import NONode from '../../../../nitroglycerin/js/nodes/NONode.js'; +import O2Node from '../../../../nitroglycerin/js/nodes/O2Node.js'; +import OF2Node from '../../../../nitroglycerin/js/nodes/OF2Node.js'; +import P4Node from '../../../../nitroglycerin/js/nodes/P4Node.js'; +import PCl3Node from '../../../../nitroglycerin/js/nodes/PCl3Node.js'; +import PCl5Node from '../../../../nitroglycerin/js/nodes/PCl5Node.js'; +import PF3Node from '../../../../nitroglycerin/js/nodes/PF3Node.js'; +import PH3Node from '../../../../nitroglycerin/js/nodes/PH3Node.js'; +import SNode from '../../../../nitroglycerin/js/nodes/SNode.js'; +import SO2Node from '../../../../nitroglycerin/js/nodes/SO2Node.js'; +import SO3Node from '../../../../nitroglycerin/js/nodes/SO3Node.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import Molecule from './Molecule.js'; + +// modules - molecules + +const MoleculeFactory = { + + C: function() { + return new Molecule( CNode, 'C', [ Element.C ] ); + }, + + Cl2: function() { + return new Molecule( Cl2Node, 'Cl2', [ Element.Cl, Element.Cl ] ); + }, + + C2H2: function() { + return new Molecule( C2H2Node, 'C2H2', [ Element.C, Element.C, Element.H, Element.H ] ); + }, + + C2H4: function() { + return new Molecule( C2H4Node, 'C2H4', [ Element.C, Element.C, Element.H, Element.H, Element.H, Element.H ] ); + }, + + C2H5Cl: function() { + return new Molecule( C2H5ClNode, 'C2H5Cl', [ Element.C, Element.C, Element.H, Element.H, Element.H, Element.H, Element.H, Element.Cl ] ); + }, + + C2H5OH: function() { + return new Molecule( C2H5OHNode, 'C2H5OH', [ Element.C, Element.C, Element.H, Element.H, Element.H, Element.H, Element.H, Element.O, Element.H ] ); + }, + + C2H6: function() { + return new Molecule( C2H6Node, 'C2H6', [ Element.C, Element.C, Element.H, Element.H, Element.H, Element.H, Element.H, Element.H ] ); + }, + + CH2O: function() { + return new Molecule( CH2ONode, 'CH2O', [ Element.C, Element.H, Element.H, Element.O ] ); + }, + + CH3OH: function() { + return new Molecule( CH3OHNode, 'CH3OH', [ Element.C, Element.H, Element.H, Element.H, Element.O, Element.H ] ); + }, + + CH4: function() { + return new Molecule( CH4Node, 'CH4', [ Element.C, Element.H, Element.H, Element.H, Element.H ] ); + }, + + CO: function() { + return new Molecule( CONode, 'CO', [ Element.C, Element.O ] ); + }, + + CO2: function() { + return new Molecule( CO2Node, 'CO2', [ Element.C, Element.O, Element.O ] ); + }, + + CS2: function() { + return new Molecule( CS2Node, 'CS2', [ Element.C, Element.S, Element.S ] ); + }, + + F2: function() { + return new Molecule( F2Node, 'F2', [ Element.F, Element.F ] ); + }, + + H2: function() { + return new Molecule( H2Node, 'H2', [ Element.H, Element.H ] ); + }, + + H2O: function() { + return new Molecule( H2ONode, 'H2O', [ Element.H, Element.H, Element.O ] ); + }, + + H2S: function() { + return new Molecule( H2SNode, 'H2S', [ Element.H, Element.H, Element.S ] ); + }, + + HF: function() { + return new Molecule( HFNode, 'HF', [ Element.H, Element.F ] ); + }, + + HCl: function() { + return new Molecule( HClNode, 'HCl', [ Element.H, Element.Cl ] ); + }, + + N2: function() { + return new Molecule( N2Node, 'N2', [ Element.N, Element.N ] ); + }, + + N2O: function() { + return new Molecule( N2ONode, 'N2O', [ Element.N, Element.N, Element.O ] ); + }, + + NH3: function() { + return new Molecule( NH3Node, 'NH3', [ Element.N, Element.H, Element.H, Element.H ] ); + }, + + NO: function() { + return new Molecule( NONode, 'NO', [ Element.N, Element.O ] ); + }, - OF2: function() { - return new Molecule( OF2Node, 'OF2', [ Element.O, Element.F, Element.F ] ); - }, + NO2: function() { + return new Molecule( NO2Node, 'NO2', [ Element.N, Element.O, Element.O ] ); + }, - P4: function() { - return new Molecule( P4Node, 'P4', [ Element.P, Element.P, Element.P, Element.P ] ); - }, + O2: function() { + return new Molecule( O2Node, 'O2', [ Element.O, Element.O ] ); + }, - PH3: function() { - return new Molecule( PH3Node, 'PH3', [ Element.P, Element.H, Element.H, Element.H ] ); - }, + OF2: function() { + return new Molecule( OF2Node, 'OF2', [ Element.O, Element.F, Element.F ] ); + }, - PCl3: function() { - return new Molecule( PCl3Node, 'PCl3', [ Element.P, Element.Cl, Element.Cl, Element.Cl ] ); - }, + P4: function() { + return new Molecule( P4Node, 'P4', [ Element.P, Element.P, Element.P, Element.P ] ); + }, - PCl5: function() { - return new Molecule( PCl5Node, 'PCl5', [ Element.P, Element.Cl, Element.Cl, Element.Cl, Element.Cl, Element.Cl ] ); - }, + PH3: function() { + return new Molecule( PH3Node, 'PH3', [ Element.P, Element.H, Element.H, Element.H ] ); + }, - PF3: function() { - return new Molecule( PF3Node, 'PF3', [ Element.P, Element.F, Element.F, Element.F ] ); - }, + PCl3: function() { + return new Molecule( PCl3Node, 'PCl3', [ Element.P, Element.Cl, Element.Cl, Element.Cl ] ); + }, - S: function() { - return new Molecule( SNode, 'S', [ Element.S ] ); - }, + PCl5: function() { + return new Molecule( PCl5Node, 'PCl5', [ Element.P, Element.Cl, Element.Cl, Element.Cl, Element.Cl, Element.Cl ] ); + }, - SO3: function() { - return new Molecule( SO3Node, 'SO3', [ Element.S, Element.O, Element.O, Element.O ] ); - }, + PF3: function() { + return new Molecule( PF3Node, 'PF3', [ Element.P, Element.F, Element.F, Element.F ] ); + }, - SO2: function() { - return new Molecule( SO2Node, 'SO2', [ Element.S, Element.O, Element.O ] ); - } - }; + S: function() { + return new Molecule( SNode, 'S', [ Element.S ] ); + }, - return balancingChemicalEquations.register( 'MoleculeFactory', MoleculeFactory ); -} ); + SO3: function() { + return new Molecule( SO3Node, 'SO3', [ Element.S, Element.O, Element.O, Element.O ] ); + }, + + SO2: function() { + return new Molecule( SO2Node, 'SO2', [ Element.S, Element.O, Element.O ] ); + } +}; + +balancingChemicalEquations.register( 'MoleculeFactory', MoleculeFactory ); +export default MoleculeFactory; \ No newline at end of file diff --git a/js/common/model/SynthesisEquation.js b/js/common/model/SynthesisEquation.js index e94f1c54..33415b84 100644 --- a/js/common/model/SynthesisEquation.js +++ b/js/common/model/SynthesisEquation.js @@ -9,91 +9,87 @@ * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const Equation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/Equation' ); - const EquationTerm = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/EquationTerm' ); - const MoleculeFactory = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/MoleculeFactory' ); - - class SynthesisEquation extends Equation { - - /** - * @param {number} r1 balanced coefficient for reactant1 - * @param {Molecule} reactant1 - * @param {number} r2 balanced coefficient for reactant2 - * @param {Molecule} reactant2 - * @param {number} p1 balanced coefficient for product1 - * @param {Molecule} product1 - * @param {Object} [options] - */ - constructor( r1, reactant1, r2, reactant2, p1, product1, options ) { - super( - [ new EquationTerm( r1, reactant1 ), new EquationTerm( r2, reactant2 ) ], - [ new EquationTerm( p1, product1 ) ], - options - ); - } - - // @public @static - - // 2 H2 + O2 -> 2 H2O - static create_2H2_O2_2H2O() { - return new SynthesisEquation( 2, MoleculeFactory.H2(), 1, MoleculeFactory.O2(), 2, MoleculeFactory.H2O() ); - } - - // H2 + F2 -> 2 HF - static create_H2_F2_2HF() { - return new SynthesisEquation( 1, MoleculeFactory.H2(), 1, MoleculeFactory.F2(), 2, MoleculeFactory.HF() ); - } - - // CH2O + H2 -> CH3OH - static create_CH2O_H2_CH3OH() { - return new SynthesisEquation( 1, MoleculeFactory.CH2O(), 1, MoleculeFactory.H2(), 1, MoleculeFactory.CH3OH() ); - } - - // C2H2 + 2 H2 -> C2H6 - static create_C2H2_2H2_C2H6() { - return new SynthesisEquation( 1, MoleculeFactory.C2H2(), 2, MoleculeFactory.H2(), 1, MoleculeFactory.C2H6() ); - } - - // C + O2 -> CO2 - static create_C_O2_CO2() { - return new SynthesisEquation( 1, MoleculeFactory.C(), 1, MoleculeFactory.O2(), 1, MoleculeFactory.CO2() ); - } - - // 2 C + O2 -> 2 CO - static create_2C_O2_2CO() { - return new SynthesisEquation( 2, MoleculeFactory.C(), 1, MoleculeFactory.O2(), 2, MoleculeFactory.CO() ); - } - - // C + 2 S -> CS2 - static create_C_2S_CS2() { - return new SynthesisEquation( 1, MoleculeFactory.C(), 2, MoleculeFactory.S(), 1, MoleculeFactory.CS2() ); - } - - // N2 + 3 H2 -> 2 NH3 - static create_N2_3H2_2NH3() { - return new SynthesisEquation( 1, MoleculeFactory.N2(), 3, MoleculeFactory.H2(), 2, MoleculeFactory.NH3() ); - } - - // 2 N2 + O2 -> 2 N2O - static create_2N2_O2_2N2O() { - return new SynthesisEquation( 2, MoleculeFactory.N2(), 1, MoleculeFactory.O2(), 2, MoleculeFactory.N2O() ); - } - - // P4 + 6 H2 -> 4 PH3 - static create_P4_6H2_4PH3() { - return new SynthesisEquation( 1, MoleculeFactory.P4(), 6, MoleculeFactory.H2(), 4, MoleculeFactory.PH3() ); - } - - // P4 + 6 F2 -> 4 PF3 - static create_P4_6F2_4PF3() { - return new SynthesisEquation( 1, MoleculeFactory.P4(), 6, MoleculeFactory.F2(), 4, MoleculeFactory.PF3() ); - } +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import Equation from './Equation.js'; +import EquationTerm from './EquationTerm.js'; +import MoleculeFactory from './MoleculeFactory.js'; + +class SynthesisEquation extends Equation { + + /** + * @param {number} r1 balanced coefficient for reactant1 + * @param {Molecule} reactant1 + * @param {number} r2 balanced coefficient for reactant2 + * @param {Molecule} reactant2 + * @param {number} p1 balanced coefficient for product1 + * @param {Molecule} product1 + * @param {Object} [options] + */ + constructor( r1, reactant1, r2, reactant2, p1, product1, options ) { + super( + [ new EquationTerm( r1, reactant1 ), new EquationTerm( r2, reactant2 ) ], + [ new EquationTerm( p1, product1 ) ], + options + ); } - return balancingChemicalEquations.register( 'SynthesisEquation', SynthesisEquation ); -} ); \ No newline at end of file + // @public @static + + // 2 H2 + O2 -> 2 H2O + static create_2H2_O2_2H2O() { + return new SynthesisEquation( 2, MoleculeFactory.H2(), 1, MoleculeFactory.O2(), 2, MoleculeFactory.H2O() ); + } + + // H2 + F2 -> 2 HF + static create_H2_F2_2HF() { + return new SynthesisEquation( 1, MoleculeFactory.H2(), 1, MoleculeFactory.F2(), 2, MoleculeFactory.HF() ); + } + + // CH2O + H2 -> CH3OH + static create_CH2O_H2_CH3OH() { + return new SynthesisEquation( 1, MoleculeFactory.CH2O(), 1, MoleculeFactory.H2(), 1, MoleculeFactory.CH3OH() ); + } + + // C2H2 + 2 H2 -> C2H6 + static create_C2H2_2H2_C2H6() { + return new SynthesisEquation( 1, MoleculeFactory.C2H2(), 2, MoleculeFactory.H2(), 1, MoleculeFactory.C2H6() ); + } + + // C + O2 -> CO2 + static create_C_O2_CO2() { + return new SynthesisEquation( 1, MoleculeFactory.C(), 1, MoleculeFactory.O2(), 1, MoleculeFactory.CO2() ); + } + + // 2 C + O2 -> 2 CO + static create_2C_O2_2CO() { + return new SynthesisEquation( 2, MoleculeFactory.C(), 1, MoleculeFactory.O2(), 2, MoleculeFactory.CO() ); + } + + // C + 2 S -> CS2 + static create_C_2S_CS2() { + return new SynthesisEquation( 1, MoleculeFactory.C(), 2, MoleculeFactory.S(), 1, MoleculeFactory.CS2() ); + } + + // N2 + 3 H2 -> 2 NH3 + static create_N2_3H2_2NH3() { + return new SynthesisEquation( 1, MoleculeFactory.N2(), 3, MoleculeFactory.H2(), 2, MoleculeFactory.NH3() ); + } + + // 2 N2 + O2 -> 2 N2O + static create_2N2_O2_2N2O() { + return new SynthesisEquation( 2, MoleculeFactory.N2(), 1, MoleculeFactory.O2(), 2, MoleculeFactory.N2O() ); + } + + // P4 + 6 H2 -> 4 PH3 + static create_P4_6H2_4PH3() { + return new SynthesisEquation( 1, MoleculeFactory.P4(), 6, MoleculeFactory.H2(), 4, MoleculeFactory.PH3() ); + } + + // P4 + 6 F2 -> 4 PF3 + static create_P4_6F2_4PF3() { + return new SynthesisEquation( 1, MoleculeFactory.P4(), 6, MoleculeFactory.F2(), 4, MoleculeFactory.PF3() ); + } +} + +balancingChemicalEquations.register( 'SynthesisEquation', SynthesisEquation ); +export default SynthesisEquation; \ No newline at end of file diff --git a/js/common/view/BalanceScaleNode.js b/js/common/view/BalanceScaleNode.js index ea01d718..677ae8e3 100644 --- a/js/common/view/BalanceScaleNode.js +++ b/js/common/view/BalanceScaleNode.js @@ -9,214 +9,211 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const AtomNode = require( 'NITROGLYCERIN/nodes/AtomNode' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const BeamNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BeamNode' ); - const Dimension2 = require( 'DOT/Dimension2' ); - const FulcrumNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/FulcrumNode' ); - const Node = require( 'SCENERY/nodes/Node' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Text = require( 'SCENERY/nodes/Text' ); - const Vector2 = require( 'DOT/Vector2' ); - - // constants - const FULCRUM_SIZE = new Dimension2( 60, 45 ); - const BEAM_LENGTH = 205; - const BEAM_THICKNESS = 6; - const NUMBER_OF_TILT_ANGLES = 6; - const COUNT_Y_SPACING = 3; - const ATOMS_IN_PILE_BASE = 5; // number of atoms along the base of each pile - const TEXT_OPTIONS = { font: new PhetFont( 18 ), fill: 'black' }; - - class BalanceScaleNode extends Node { - - /** - * @param {NITROGLYCERIN.Element} element the atom that we're displaying on the scale - * @param {Property.} leftNumberOfAtomsProperty number of atoms on left (reactants) side of the beam - * @param {Property.} rightNumberOfAtomsProperty number of atoms on right (products) side of the beam - * @param {Property.} highlightedProperty - * @param {Object} [options] - */ - constructor( element, leftNumberOfAtomsProperty, rightNumberOfAtomsProperty, highlightedProperty, options ) { - - super(); - - this.element = element; // @private - this.leftNumberOfAtomsProperty = leftNumberOfAtomsProperty; // @private - this.rightNumberOfAtomsProperty = rightNumberOfAtomsProperty; // @private - - // fulcrum - const fulcrumNode = new FulcrumNode( element, FULCRUM_SIZE ); - - // @private beam - this.beamNode = new BeamNode( BEAM_LENGTH, BEAM_THICKNESS, { - bottom: 0, - transformBounds: true /* see issue #77 */ - } ); - - // left pile & count - this.leftPileNode = new Node(); // @private - this.leftCountNode = new Text( leftNumberOfAtomsProperty.get(), TEXT_OPTIONS ); // @private - - // right pile & count - this.rightPileNode = new Node(); // @private - this.rightCountNode = new Text( rightNumberOfAtomsProperty.get(), TEXT_OPTIONS ); // @private - - // @private parent for both piles, to simplify rotation - this.pilesParent = new Node( { - children: [ this.leftPileNode, this.leftCountNode, this.rightPileNode, this.rightCountNode ], - transformBounds: true /* see issue #77 */ - } ); - - options.children = [ fulcrumNode, this.beamNode, this.pilesParent ]; - this.mutate( options ); - - // highlight the beam - const highlightObserver = highlighted => this.beamNode.setHighlighted( highlighted ); - highlightedProperty.link( highlightObserver ); - - // update piles - const updateNodeBind = this.updateNode.bind( this ); - leftNumberOfAtomsProperty.lazyLink( updateNodeBind ); - rightNumberOfAtomsProperty.lazyLink( updateNodeBind ); - this.updateNode(); - - // unlink from Properties - this.balanceScaleNodeDispose = () => { - highlightedProperty.unlink( highlightObserver ); - leftNumberOfAtomsProperty.unlink( updateNodeBind ); - rightNumberOfAtomsProperty.unlink( updateNodeBind ); - }; - } - // @public - dispose() { - this.balanceScaleNodeDispose(); - super.dispose(); - } +import Dimension2 from '../../../../dot/js/Dimension2.js'; +import Vector2 from '../../../../dot/js/Vector2.js'; +import AtomNode from '../../../../nitroglycerin/js/nodes/AtomNode.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../BCEConstants.js'; +import BeamNode from './BeamNode.js'; +import FulcrumNode from './FulcrumNode.js'; + +// constants +const FULCRUM_SIZE = new Dimension2( 60, 45 ); +const BEAM_LENGTH = 205; +const BEAM_THICKNESS = 6; +const NUMBER_OF_TILT_ANGLES = 6; +const COUNT_Y_SPACING = 3; +const ATOMS_IN_PILE_BASE = 5; // number of atoms along the base of each pile +const TEXT_OPTIONS = { font: new PhetFont( 18 ), fill: 'black' }; + +class BalanceScaleNode extends Node { - /** - * Places piles of atoms on the ends of the beam, with a count of the number of - * atoms above each pile. Then rotates the beam and stuff on it to indicate the - * relative balance between the left and right piles. - * @private - */ - updateNode() { - - // update piles on beam in neutral orientation - this.beamNode.setRotation( 0 ); - this.pilesParent.setRotation( 0 ); - - const leftNumberOfAtoms = this.leftNumberOfAtomsProperty.get(); - const rightNumberOfAtoms = this.rightNumberOfAtomsProperty.get(); - - // update piles - updatePile( this.element, leftNumberOfAtoms, this.leftPileNode, this.leftCountNode, this.beamNode.left + 0.25 * this.beamNode.width, this.beamNode.top ); - updatePile( this.element, rightNumberOfAtoms, this.rightPileNode, this.rightCountNode, this.beamNode.right - 0.25 * this.beamNode.width, this.beamNode.top ); - - // rotate beam and piles on fulcrum - const maxAngle = ( Math.PI / 2 ) - Math.acos( FULCRUM_SIZE.height / ( BEAM_LENGTH / 2 ) ); - const difference = rightNumberOfAtoms - leftNumberOfAtoms; - let angle = 0; - if ( Math.abs( difference ) >= NUMBER_OF_TILT_ANGLES ) { - // max tilt - const sign = Math.abs( difference ) / difference; - angle = sign * maxAngle; - } - else { - // partial tilt - angle = difference * ( maxAngle / NUMBER_OF_TILT_ANGLES ); - } - this.beamNode.setRotation( angle ); - this.pilesParent.setRotation( angle ); - } + /** + * @param {NITROGLYCERIN.Element} element the atom that we're displaying on the scale + * @param {Property.} leftNumberOfAtomsProperty number of atoms on left (reactants) side of the beam + * @param {Property.} rightNumberOfAtomsProperty number of atoms on right (products) side of the beam + * @param {Property.} highlightedProperty + * @param {Object} [options] + */ + constructor( element, leftNumberOfAtomsProperty, rightNumberOfAtomsProperty, highlightedProperty, options ) { + + super(); + + this.element = element; // @private + this.leftNumberOfAtomsProperty = leftNumberOfAtomsProperty; // @private + this.rightNumberOfAtomsProperty = rightNumberOfAtomsProperty; // @private + + // fulcrum + const fulcrumNode = new FulcrumNode( element, FULCRUM_SIZE ); + + // @private beam + this.beamNode = new BeamNode( BEAM_LENGTH, BEAM_THICKNESS, { + bottom: 0, + transformBounds: true /* see issue #77 */ + } ); + + // left pile & count + this.leftPileNode = new Node(); // @private + this.leftCountNode = new Text( leftNumberOfAtomsProperty.get(), TEXT_OPTIONS ); // @private + + // right pile & count + this.rightPileNode = new Node(); // @private + this.rightCountNode = new Text( rightNumberOfAtomsProperty.get(), TEXT_OPTIONS ); // @private + + // @private parent for both piles, to simplify rotation + this.pilesParent = new Node( { + children: [ this.leftPileNode, this.leftCountNode, this.rightPileNode, this.rightCountNode ], + transformBounds: true /* see issue #77 */ + } ); + + options.children = [ fulcrumNode, this.beamNode, this.pilesParent ]; + this.mutate( options ); + + // highlight the beam + const highlightObserver = highlighted => this.beamNode.setHighlighted( highlighted ); + highlightedProperty.link( highlightObserver ); + + // update piles + const updateNodeBind = this.updateNode.bind( this ); + leftNumberOfAtomsProperty.lazyLink( updateNodeBind ); + rightNumberOfAtomsProperty.lazyLink( updateNodeBind ); + this.updateNode(); + + // unlink from Properties + this.balanceScaleNodeDispose = () => { + highlightedProperty.unlink( highlightObserver ); + leftNumberOfAtomsProperty.unlink( updateNodeBind ); + rightNumberOfAtomsProperty.unlink( updateNodeBind ); + }; + } + + // @public + dispose() { + this.balanceScaleNodeDispose(); + super.dispose(); } /** - * Updates a triangular pile of atoms. - * Atoms are populated one row at a time, starting from the base of the triangle and working up. - * To improve performance: - * - Atoms are added to the pile as needed. - * - Atoms are never removed from the pile; they stay in the pile for the lifetime of this node. - * - The visibility of atoms is adjusted to show the correct number of atoms. - * - * @param {Element} element - * @param {number} numberOfAtoms number of atoms that will be visible in the pile - * @param {Node} pileNode pile that will be modified - * @param {Node} countNode displays numberOfAtoms - * @param {number} pileCenterX x-coordinate of the pile's center, relative to the beam - * @param {number} beamTop y-coordinate of the beam's top + * Places piles of atoms on the ends of the beam, with a count of the number of + * atoms above each pile. Then rotates the beam and stuff on it to indicate the + * relative balance between the left and right piles. + * @private */ - function updatePile( element, numberOfAtoms, pileNode, countNode, pileCenterX, beamTop ) { - - const nodesInPile = pileNode.getChildrenCount(); // how many atom nodes are currently in the pile - let pile = 0; // which pile we're working on, layered back-to-front, offset left-to-right - let row = 0; // the row number, bottom row is zero - let atomsInRow = 0; // number of atoms that have been added to the current row - let x = 0; - let y = 0; - let atomNode; - - for ( let i = 0; i < Math.max( nodesInPile, numberOfAtoms ); i++ ) { - - if ( i < nodesInPile ) { - // set visibility of an atom that's already in the pile - atomNode = pileNode.getChildAt( i ); - atomNode.visible = ( i < numberOfAtoms ); - } - else { - // add an atom node - atomNode = new AtomNode( element, BCEConstants.ATOM_OPTIONS ); - atomNode.scale( BCEConstants.MOLECULE_SCALE_FACTOR ); - pileNode.addChild( atomNode ); - atomNode.translation = new Vector2( x + ( atomNode.width / 2 ), y - ( atomNode.height / 2 ) ); - } - - // determine position of next atom - atomsInRow++; - if ( atomsInRow < ATOMS_IN_PILE_BASE - row ) { - // continue with current row - x = atomNode.right; - } - else if ( row < ATOMS_IN_PILE_BASE - 1 ) { - // move to next row in current triangular pile - row++; - atomsInRow = 0; - x = ( pile + row ) * ( atomNode.width / 2 ); - y = -( row * 0.85 * atomNode.height ); - } - else { - // start a new pile, offset from the previous pile - row = 0; - pile++; - atomsInRow = 0; - x = pile * ( atomNode.width / 2 ); - y = 0; - } + updateNode() { + + // update piles on beam in neutral orientation + this.beamNode.setRotation( 0 ); + this.pilesParent.setRotation( 0 ); + + const leftNumberOfAtoms = this.leftNumberOfAtomsProperty.get(); + const rightNumberOfAtoms = this.rightNumberOfAtomsProperty.get(); + + // update piles + updatePile( this.element, leftNumberOfAtoms, this.leftPileNode, this.leftCountNode, this.beamNode.left + 0.25 * this.beamNode.width, this.beamNode.top ); + updatePile( this.element, rightNumberOfAtoms, this.rightPileNode, this.rightCountNode, this.beamNode.right - 0.25 * this.beamNode.width, this.beamNode.top ); + + // rotate beam and piles on fulcrum + const maxAngle = ( Math.PI / 2 ) - Math.acos( FULCRUM_SIZE.height / ( BEAM_LENGTH / 2 ) ); + const difference = rightNumberOfAtoms - leftNumberOfAtoms; + let angle = 0; + if ( Math.abs( difference ) >= NUMBER_OF_TILT_ANGLES ) { + // max tilt + const sign = Math.abs( difference ) / difference; + angle = sign * maxAngle; + } + else { + // partial tilt + angle = difference * ( maxAngle / NUMBER_OF_TILT_ANGLES ); } + this.beamNode.setRotation( angle ); + this.pilesParent.setRotation( angle ); + } +} - // count display - countNode.text = numberOfAtoms; +/** + * Updates a triangular pile of atoms. + * Atoms are populated one row at a time, starting from the base of the triangle and working up. + * To improve performance: + * - Atoms are added to the pile as needed. + * - Atoms are never removed from the pile; they stay in the pile for the lifetime of this node. + * - The visibility of atoms is adjusted to show the correct number of atoms. + * + * @param {Element} element + * @param {number} numberOfAtoms number of atoms that will be visible in the pile + * @param {Node} pileNode pile that will be modified + * @param {Node} countNode displays numberOfAtoms + * @param {number} pileCenterX x-coordinate of the pile's center, relative to the beam + * @param {number} beamTop y-coordinate of the beam's top + */ +function updatePile( element, numberOfAtoms, pileNode, countNode, pileCenterX, beamTop ) { + + const nodesInPile = pileNode.getChildrenCount(); // how many atom nodes are currently in the pile + let pile = 0; // which pile we're working on, layered back-to-front, offset left-to-right + let row = 0; // the row number, bottom row is zero + let atomsInRow = 0; // number of atoms that have been added to the current row + let x = 0; + let y = 0; + let atomNode; + + for ( let i = 0; i < Math.max( nodesInPile, numberOfAtoms ); i++ ) { + + if ( i < nodesInPile ) { + // set visibility of an atom that's already in the pile + atomNode = pileNode.getChildAt( i ); + atomNode.visible = ( i < numberOfAtoms ); + } + else { + // add an atom node + atomNode = new AtomNode( element, BCEConstants.ATOM_OPTIONS ); + atomNode.scale( BCEConstants.MOLECULE_SCALE_FACTOR ); + pileNode.addChild( atomNode ); + atomNode.translation = new Vector2( x + ( atomNode.width / 2 ), y - ( atomNode.height / 2 ) ); + } - // layout - if ( pileNode.visibleBounds.isEmpty() ) { - // pile is empty, just deal with count - countNode.centerX = pileCenterX; - countNode.bottom = beamTop - COUNT_Y_SPACING; + // determine position of next atom + atomsInRow++; + if ( atomsInRow < ATOMS_IN_PILE_BASE - row ) { + // continue with current row + x = atomNode.right; + } + else if ( row < ATOMS_IN_PILE_BASE - 1 ) { + // move to next row in current triangular pile + row++; + atomsInRow = 0; + x = ( pile + row ) * ( atomNode.width / 2 ); + y = -( row * 0.85 * atomNode.height ); } else { - // account for invisible atoms in the pile - pileNode.centerX = pileCenterX + ( pileNode.width - pileNode.visibleBounds.width ) / 2; - pileNode.bottom = beamTop + 1; - countNode.centerX = pileCenterX; - countNode.bottom = pileNode.visibleBounds.top - COUNT_Y_SPACING; + // start a new pile, offset from the previous pile + row = 0; + pile++; + atomsInRow = 0; + x = pile * ( atomNode.width / 2 ); + y = 0; } } - return balancingChemicalEquations.register( 'BalanceScaleNode', BalanceScaleNode ); -} ); \ No newline at end of file + // count display + countNode.text = numberOfAtoms; + + // layout + if ( pileNode.visibleBounds.isEmpty() ) { + // pile is empty, just deal with count + countNode.centerX = pileCenterX; + countNode.bottom = beamTop - COUNT_Y_SPACING; + } + else { + // account for invisible atoms in the pile + pileNode.centerX = pileCenterX + ( pileNode.width - pileNode.visibleBounds.width ) / 2; + pileNode.bottom = beamTop + 1; + countNode.centerX = pileCenterX; + countNode.bottom = pileNode.visibleBounds.top - COUNT_Y_SPACING; + } +} + +balancingChemicalEquations.register( 'BalanceScaleNode', BalanceScaleNode ); +export default BalanceScaleNode; \ No newline at end of file diff --git a/js/common/view/BalanceScalesNode.js b/js/common/view/BalanceScalesNode.js index 6b333cd9..83fe9eb4 100644 --- a/js/common/view/BalanceScalesNode.js +++ b/js/common/view/BalanceScalesNode.js @@ -11,128 +11,125 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const BalanceScaleNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BalanceScaleNode' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const NumberProperty = require( 'AXON/NumberProperty' ); - - class BalanceScalesNode extends Node { - - /** - * @param {Property.} equationProperty the equation that the scales are representing - * @param {HorizontalAligner} aligner provides layout information to ensure horizontal alignment with other user-interface elements - * @param {Object} [options] - */ - constructor( equationProperty, aligner, options ) { - - options = merge( { - bottom: 0, - fulcrumSpacing: 237, // horizontal spacing between the tips of the fulcrums - dualFulcrumSpacing: 237 // horizontal spacing when we have 2 scales, see issue #91 - }, options ); - - super(); - - this.equationProperty = equationProperty; // @private - this.aligner = aligner; // @private - this.constantBottom = options.bottom; // @private - this.reactantCountProperties = {}; // @private maps {string} Element.symbol to {Property.} count of the element - this.productCountProperties = {}; // @private maps {string} Element.symbol to {Property.} counts of the element - this.fulcrumSpacing = options.fulcrumSpacing; // @private - this.dualFulcrumSpacing = options.dualFulcrumSpacing; // @private - - // Wire coefficients observer to current equation. - const coefficientsObserver = this.updateCounts.bind( this ); - equationProperty.link( ( newEquation, oldEquation ) => { - this.updateNode(); - if ( oldEquation ) { oldEquation.removeCoefficientsObserver( coefficientsObserver ); } - newEquation.addCoefficientsObserver( coefficientsObserver ); - } ); - this.mutate( options ); - } +import NumberProperty from '../../../../axon/js/NumberProperty.js'; +import merge from '../../../../phet-core/js/merge.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BalanceScaleNode from './BalanceScaleNode.js'; + +class BalanceScalesNode extends Node { + + /** + * @param {Property.} equationProperty the equation that the scales are representing + * @param {HorizontalAligner} aligner provides layout information to ensure horizontal alignment with other user-interface elements + * @param {Object} [options] + */ + constructor( equationProperty, aligner, options ) { + + options = merge( { + bottom: 0, + fulcrumSpacing: 237, // horizontal spacing between the tips of the fulcrums + dualFulcrumSpacing: 237 // horizontal spacing when we have 2 scales, see issue #91 + }, options ); + + super(); + + this.equationProperty = equationProperty; // @private + this.aligner = aligner; // @private + this.constantBottom = options.bottom; // @private + this.reactantCountProperties = {}; // @private maps {string} Element.symbol to {Property.} count of the element + this.productCountProperties = {}; // @private maps {string} Element.symbol to {Property.} counts of the element + this.fulcrumSpacing = options.fulcrumSpacing; // @private + this.dualFulcrumSpacing = options.dualFulcrumSpacing; // @private + + // Wire coefficients observer to current equation. + const coefficientsObserver = this.updateCounts.bind( this ); + equationProperty.link( ( newEquation, oldEquation ) => { + this.updateNode(); + if ( oldEquation ) { oldEquation.removeCoefficientsObserver( coefficientsObserver ); } + newEquation.addCoefficientsObserver( coefficientsObserver ); + } ); + + this.mutate( options ); + } - // No dispose needed, instances of this type persist for lifetime of the sim. - - /** - * Update the node when it becomes visible. - * @param visible - * @override - * @public - */ - setVisible( visible ) { - const wasVisible = this.visible; - super.setVisible( visible ); - if ( !wasVisible && visible ) { - this.updateNode(); - } + // No dispose needed, instances of this type persist for lifetime of the sim. + + /** + * Update the node when it becomes visible. + * @param visible + * @override + * @public + */ + setVisible( visible ) { + const wasVisible = this.visible; + super.setVisible( visible ); + if ( !wasVisible && visible ) { + this.updateNode(); } + } - /** - * Updates this node's entire geometry and layout. - * @private - */ - updateNode() { - if ( this.visible ) { - - // dispose of children before calling removeAllChildren - this.getChildren().forEach( child => { - if ( child.dispose ) { - child.dispose(); - } - } ); - - // remove all nodes and clear the maps - this.removeAllChildren(); - this.reactantCountProperties = {}; - this.productCountProperties = {}; - - const atomCounts = this.equationProperty.get().getAtomCounts(); // [AtomCount] - const fulcrumSpacing = ( atomCounts.length === 2 ) ? this.dualFulcrumSpacing : this.fulcrumSpacing; - let x = 0; - for ( let i = 0; i < atomCounts.length; i++ ) { - - const atomCount = atomCounts[ i ]; - - // populate the maps - const leftCountProperty = new NumberProperty( atomCount.reactantsCount, { numberType: 'Integer' } ); - const rightCountProperty = new NumberProperty( atomCount.productsCount, { numberType: 'Integer' } ); - this.reactantCountProperties[ atomCount.element.symbol ] = leftCountProperty; - this.productCountProperties[ atomCount.element.symbol ] = rightCountProperty; - - // add a scale node - const scaleNode = new BalanceScaleNode( atomCount.element, leftCountProperty, rightCountProperty, this.equationProperty.get().balancedProperty, { x: x } ); - this.addChild( scaleNode ); - - x += fulcrumSpacing; + /** + * Updates this node's entire geometry and layout. + * @private + */ + updateNode() { + if ( this.visible ) { + + // dispose of children before calling removeAllChildren + this.getChildren().forEach( child => { + if ( child.dispose ) { + child.dispose(); } + } ); + + // remove all nodes and clear the maps + this.removeAllChildren(); + this.reactantCountProperties = {}; + this.productCountProperties = {}; - this.centerX = this.aligner.getScreenCenterX(); - this.bottom = this.constantBottom; - this.updateCounts(); + const atomCounts = this.equationProperty.get().getAtomCounts(); // [AtomCount] + const fulcrumSpacing = ( atomCounts.length === 2 ) ? this.dualFulcrumSpacing : this.fulcrumSpacing; + let x = 0; + for ( let i = 0; i < atomCounts.length; i++ ) { + + const atomCount = atomCounts[ i ]; + + // populate the maps + const leftCountProperty = new NumberProperty( atomCount.reactantsCount, { numberType: 'Integer' } ); + const rightCountProperty = new NumberProperty( atomCount.productsCount, { numberType: 'Integer' } ); + this.reactantCountProperties[ atomCount.element.symbol ] = leftCountProperty; + this.productCountProperties[ atomCount.element.symbol ] = rightCountProperty; + + // add a scale node + const scaleNode = new BalanceScaleNode( atomCount.element, leftCountProperty, rightCountProperty, this.equationProperty.get().balancedProperty, { x: x } ); + this.addChild( scaleNode ); + + x += fulcrumSpacing; } + + this.centerX = this.aligner.getScreenCenterX(); + this.bottom = this.constantBottom; + this.updateCounts(); } + } - /** - * Updates the atom counts for each scale. - * @private - */ - updateCounts() { - if ( this.visible ) { - const atomCounts = this.equationProperty.get().getAtomCounts(); - for ( let i = 0; i < atomCounts.length; i++ ) { - const atomCount = atomCounts[ i ]; - this.reactantCountProperties[ atomCount.element.symbol ].set( atomCount.reactantsCount ); - this.productCountProperties[ atomCount.element.symbol ].set( atomCount.productsCount ); - } + /** + * Updates the atom counts for each scale. + * @private + */ + updateCounts() { + if ( this.visible ) { + const atomCounts = this.equationProperty.get().getAtomCounts(); + for ( let i = 0; i < atomCounts.length; i++ ) { + const atomCount = atomCounts[ i ]; + this.reactantCountProperties[ atomCount.element.symbol ].set( atomCount.reactantsCount ); + this.productCountProperties[ atomCount.element.symbol ].set( atomCount.productsCount ); } } } +} - return balancingChemicalEquations.register( 'BalanceScalesNode', BalanceScalesNode ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'BalanceScalesNode', BalanceScalesNode ); +export default BalanceScalesNode; \ No newline at end of file diff --git a/js/common/view/BarChartsNode.js b/js/common/view/BarChartsNode.js index c484a7a6..d33df3c4 100644 --- a/js/common/view/BarChartsNode.js +++ b/js/common/view/BarChartsNode.js @@ -12,152 +12,149 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BarNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BarNode' ); - const EqualityOperatorNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/EqualityOperatorNode' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const NumberProperty = require( 'AXON/NumberProperty' ); - const Vector2 = require( 'DOT/Vector2' ); - - class BarChartsNode extends Node { - - /** - * @param {Property} equationProperty the equation that the scales are representing - * @param {HorizontalAligner} aligner provides layout information to ensure horizontal alignment with other user-interface elements - * @param {Object} [options] - */ - constructor( equationProperty, aligner, options ) { - - options = merge( {}, options ); - - super(); - - this.equationProperty = equationProperty; // @private - this.aligner = aligner; // @private - this.reactantCountProperties = {}; // @private maps {string} Element.symbol to {Property.} count of the element - this.productCountProperties = {}; // @private maps {string} Element.symbol to {Property.} counts of the element - - this.reactantBarsParent = new Node(); // @private - this.productBarsParent = new Node(); // @private - - // @private - const equalityOperatorNode = new EqualityOperatorNode( equationProperty, { - center: new Vector2( aligner.getScreenCenterX(), -40 ) - } ); - - options.children = [ this.reactantBarsParent, this.productBarsParent, equalityOperatorNode ]; - - // Wire coefficients observer to current equation. - const coefficientsObserver = this.updateCounts.bind( this ); - equationProperty.link( ( newEquation, oldEquation ) => { - this.updateNode(); - if ( oldEquation ) { oldEquation.removeCoefficientsObserver( coefficientsObserver ); } - newEquation.addCoefficientsObserver( coefficientsObserver ); - } ); - - this.mutate( options ); - } - // No dispose needed, instances of this type persist for lifetime of the sim. - - /** - * Update the node when it becomes visible. - * @param visible - * @override - * @public - */ - setVisible( visible ) { - const wasVisible = this.visible; - super.setVisible( visible ); - if ( !wasVisible && visible ) { - this.updateNode(); - } - } +import NumberProperty from '../../../../axon/js/NumberProperty.js'; +import Vector2 from '../../../../dot/js/Vector2.js'; +import merge from '../../../../phet-core/js/merge.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BarNode from './BarNode.js'; +import EqualityOperatorNode from './EqualityOperatorNode.js'; - /** - * Updates this node's entire geometry and layout - * @private - */ - updateNode() { - if ( this.visible ) { - const atomCounts = this.equationProperty.get().getAtomCounts(); - - this.reactantCountProperties = this.updateBars( - this.reactantBarsParent, - atomCounts, - atomCount => atomCount.reactantsCount, - this.aligner.getReactantsBoxCenterX() - ); - - this.productCountProperties = this.updateBars( - this.productBarsParent, - atomCounts, - atomCount => atomCount.productsCount, - this.aligner.getProductsBoxCenterX() - ); - - this.updateCounts(); - } - } +class BarChartsNode extends Node { - /** - * Updates one set of bars (reactants or products). - * @param {Node} parentNode - * @param {AtomCount[]} atomCounts counts of each atom in the equation - * @param {function} getCount 1 parameter {AtomCount}, return {number}, either the reactants or products count - * @param {number} centerX centerX of the chart - * @private - */ - updateBars( parentNode, atomCounts, getCount, centerX ) { + /** + * @param {Property} equationProperty the equation that the scales are representing + * @param {HorizontalAligner} aligner provides layout information to ensure horizontal alignment with other user-interface elements + * @param {Object} [options] + */ + constructor( equationProperty, aligner, options ) { - // dispose of children before calling removeAllChildren - parentNode.getChildren().forEach( child => { - if ( child.dispose() ) { - child.dispose(); - } - } ); + options = merge( {}, options ); - parentNode.removeAllChildren(); // remove all the bar nodes + super(); - const countProperties = {}; // clear the map + this.equationProperty = equationProperty; // @private + this.aligner = aligner; // @private + this.reactantCountProperties = {}; // @private maps {string} Element.symbol to {Property.} count of the element + this.productCountProperties = {}; // @private maps {string} Element.symbol to {Property.} counts of the element - let barCenterX = 0; - for ( let i = 0; i < atomCounts.length; i++ ) { - const atomCount = atomCounts[ i ]; - // populate the map - const countProperty = new NumberProperty( getCount( atomCount ), { numberType: 'Integer' } ); - countProperties[ atomCount.element.symbol ] = countProperty; - // add a bar node - const barNode = new BarNode( atomCount.element, countProperty, { centerX: barCenterX, bottom: 0 } ); - parentNode.addChild( barNode ); - barCenterX = barNode.centerX + 100; - } + this.reactantBarsParent = new Node(); // @private + this.productBarsParent = new Node(); // @private + + // @private + const equalityOperatorNode = new EqualityOperatorNode( equationProperty, { + center: new Vector2( aligner.getScreenCenterX(), -40 ) + } ); + + options.children = [ this.reactantBarsParent, this.productBarsParent, equalityOperatorNode ]; + + // Wire coefficients observer to current equation. + const coefficientsObserver = this.updateCounts.bind( this ); + equationProperty.link( ( newEquation, oldEquation ) => { + this.updateNode(); + if ( oldEquation ) { oldEquation.removeCoefficientsObserver( coefficientsObserver ); } + newEquation.addCoefficientsObserver( coefficientsObserver ); + } ); + + this.mutate( options ); + } + + // No dispose needed, instances of this type persist for lifetime of the sim. + + /** + * Update the node when it becomes visible. + * @param visible + * @override + * @public + */ + setVisible( visible ) { + const wasVisible = this.visible; + super.setVisible( visible ); + if ( !wasVisible && visible ) { + this.updateNode(); + } + } - parentNode.centerX = centerX; + /** + * Updates this node's entire geometry and layout + * @private + */ + updateNode() { + if ( this.visible ) { + const atomCounts = this.equationProperty.get().getAtomCounts(); + + this.reactantCountProperties = this.updateBars( + this.reactantBarsParent, + atomCounts, + atomCount => atomCount.reactantsCount, + this.aligner.getReactantsBoxCenterX() + ); + + this.productCountProperties = this.updateBars( + this.productBarsParent, + atomCounts, + atomCount => atomCount.productsCount, + this.aligner.getProductsBoxCenterX() + ); + + this.updateCounts(); + } + } - return countProperties; + /** + * Updates one set of bars (reactants or products). + * @param {Node} parentNode + * @param {AtomCount[]} atomCounts counts of each atom in the equation + * @param {function} getCount 1 parameter {AtomCount}, return {number}, either the reactants or products count + * @param {number} centerX centerX of the chart + * @private + */ + updateBars( parentNode, atomCounts, getCount, centerX ) { + + // dispose of children before calling removeAllChildren + parentNode.getChildren().forEach( child => { + if ( child.dispose() ) { + child.dispose(); + } + } ); + + parentNode.removeAllChildren(); // remove all the bar nodes + + const countProperties = {}; // clear the map + + let barCenterX = 0; + for ( let i = 0; i < atomCounts.length; i++ ) { + const atomCount = atomCounts[ i ]; + // populate the map + const countProperty = new NumberProperty( getCount( atomCount ), { numberType: 'Integer' } ); + countProperties[ atomCount.element.symbol ] = countProperty; + // add a bar node + const barNode = new BarNode( atomCount.element, countProperty, { centerX: barCenterX, bottom: 0 } ); + parentNode.addChild( barNode ); + barCenterX = barNode.centerX + 100; } - /** - * Updates the atom counts for each bar. - * @private - */ - updateCounts() { - if ( this.visible ) { - const atomCounts = this.equationProperty.get().getAtomCounts(); - for ( let i = 0; i < atomCounts.length; i++ ) { - const atomCount = atomCounts[ i ]; - this.reactantCountProperties[ atomCount.element.symbol ].set( atomCount.reactantsCount ); - this.productCountProperties[ atomCount.element.symbol ].set( atomCount.productsCount ); - } + parentNode.centerX = centerX; + + return countProperties; + } + + /** + * Updates the atom counts for each bar. + * @private + */ + updateCounts() { + if ( this.visible ) { + const atomCounts = this.equationProperty.get().getAtomCounts(); + for ( let i = 0; i < atomCounts.length; i++ ) { + const atomCount = atomCounts[ i ]; + this.reactantCountProperties[ atomCount.element.symbol ].set( atomCount.reactantsCount ); + this.productCountProperties[ atomCount.element.symbol ].set( atomCount.productsCount ); } } } +} - return balancingChemicalEquations.register( 'BarChartsNode', BarChartsNode ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'BarChartsNode', BarChartsNode ); +export default BarChartsNode; \ No newline at end of file diff --git a/js/common/view/BarNode.js b/js/common/view/BarNode.js index 81a2b9cf..a63a07a7 100644 --- a/js/common/view/BarNode.js +++ b/js/common/view/BarNode.js @@ -11,108 +11,105 @@ * @author Vasily Shakhov(mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - - // modules - const AtomNode = require( 'NITROGLYCERIN/nodes/AtomNode' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const Dimension2 = require( 'DOT/Dimension2' ); - const HBox = require( 'SCENERY/nodes/HBox' ); - const HStrut = require( 'SCENERY/nodes/HStrut' ); - const Path = require( 'SCENERY/nodes/Path' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Shape = require( 'KITE/Shape' ); - const Text = require( 'SCENERY/nodes/Text' ); - const VBox = require( 'SCENERY/nodes/VBox' ); - - // constants - const MAX_NUMBER_OF_ATOMS = 12; // bar changes to an arrow above this number - const MAX_BAR_SIZE = new Dimension2( 40, 60 ); - const BAR_LINE_WIDTH = 1.5; - const ARROW_SIZE = new Dimension2( 1.5 * MAX_BAR_SIZE.width, 15 ); - - class BarNode extends VBox { - - /** - * @param {NITROGLYCERIN.Element} element the atom that we're displaying on the bar - * @param {Property.} numberOfAtomsProperty number of elements - * @param {Object} [options] - */ - constructor( element, numberOfAtomsProperty, options ) { + +import Dimension2 from '../../../../dot/js/Dimension2.js'; +import Shape from '../../../../kite/js/Shape.js'; +import AtomNode from '../../../../nitroglycerin/js/nodes/AtomNode.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import HBox from '../../../../scenery/js/nodes/HBox.js'; +import HStrut from '../../../../scenery/js/nodes/HStrut.js'; +import Path from '../../../../scenery/js/nodes/Path.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import VBox from '../../../../scenery/js/nodes/VBox.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../BCEConstants.js'; + +// constants +const MAX_NUMBER_OF_ATOMS = 12; // bar changes to an arrow above this number +const MAX_BAR_SIZE = new Dimension2( 40, 60 ); +const BAR_LINE_WIDTH = 1.5; +const ARROW_SIZE = new Dimension2( 1.5 * MAX_BAR_SIZE.width, 15 ); + +class BarNode extends VBox { + + /** + * @param {NITROGLYCERIN.Element} element the atom that we're displaying on the bar + * @param {Property.} numberOfAtomsProperty number of elements + * @param {Object} [options] + */ + constructor( element, numberOfAtomsProperty, options ) { + + // number of atoms + const numberNode = new Text( '?', { font: new PhetFont( 18 ) } ); + + // bar + const barNode = new Path( Shape.rect( 0, 0, 1, 1 ), { + fill: element.color, + stroke: 'black', + lineWidth: BAR_LINE_WIDTH + } ); + + // atom symbol + const symbolNode = new Text( element.symbol, { font: new PhetFont( 24 ) } ); + + // atom icon + const iconNode = new AtomNode( element, BCEConstants.ATOM_OPTIONS ); + iconNode.scale( BCEConstants.MOLECULE_SCALE_FACTOR ); + + // horizontal strut, to prevent resizing + const hStrut = new HStrut( MAX_BAR_SIZE.width + BAR_LINE_WIDTH ); + + options.children = [ hStrut, numberNode, barNode, new HBox( { + children: [ iconNode, symbolNode ], + spacing: 3 + } ) ]; + super( options ); + + const numberOfAtomsListener = numberOfAtoms => { // number of atoms - const numberNode = new Text( '?', { font: new PhetFont( 18 ) } ); + numberNode.text = numberOfAtoms + ''; // bar - const barNode = new Path( Shape.rect( 0, 0, 1, 1 ), { - fill: element.color, - stroke: 'black', - lineWidth: BAR_LINE_WIDTH - } ); - - // atom symbol - const symbolNode = new Text( element.symbol, { font: new PhetFont( 24 ) } ); - - // atom icon - const iconNode = new AtomNode( element, BCEConstants.ATOM_OPTIONS ); - iconNode.scale( BCEConstants.MOLECULE_SCALE_FACTOR ); - - // horizontal strut, to prevent resizing - const hStrut = new HStrut( MAX_BAR_SIZE.width + BAR_LINE_WIDTH ); - - options.children = [ hStrut, numberNode, barNode, new HBox( { - children: [ iconNode, symbolNode ], - spacing: 3 - } ) ]; - super( options ); - - const numberOfAtomsListener = numberOfAtoms => { - - // number of atoms - numberNode.text = numberOfAtoms + ''; - - // bar - let barShape; - if ( numberOfAtoms <= MAX_NUMBER_OF_ATOMS ) { - // rectangular bar - const height = MAX_BAR_SIZE.height * ( numberOfAtoms / MAX_NUMBER_OF_ATOMS ); - barShape = Shape.rect( 0, -height, MAX_BAR_SIZE.width, height ); - } - else { - // bar with upward-pointing arrow, path is specified clockwise from arrow tip. - barShape = new Shape() - .moveTo( 0, -MAX_BAR_SIZE.height ) - .lineTo( ARROW_SIZE.width / 2, -( MAX_BAR_SIZE.height - ARROW_SIZE.height ) ) - .lineTo( MAX_BAR_SIZE.width / 2, -( MAX_BAR_SIZE.height - ARROW_SIZE.height ) ) - .lineTo( MAX_BAR_SIZE.width / 2, 0 ) - .lineTo( -MAX_BAR_SIZE.width / 2, 0 ) - .lineTo( -MAX_BAR_SIZE.width / 2, -( MAX_BAR_SIZE.height - ARROW_SIZE.height ) ) - .lineTo( -ARROW_SIZE.width / 2, -( MAX_BAR_SIZE.height - ARROW_SIZE.height ) ) - .close(); - } - barNode.setShape( barShape ); - barNode.visible = ( numberOfAtoms > 0 ); - - this.bottom = 0; - }; - - // when the number of atoms changes ... - numberOfAtomsProperty.link( numberOfAtomsListener ); - - // @private - this.disposeBarNode = function() { - numberOfAtomsProperty.unlink( numberOfAtomsListener ); - }; - } - - // @public - dispose() { - this.disposeBarNode(); - super.dispose(); - } + let barShape; + if ( numberOfAtoms <= MAX_NUMBER_OF_ATOMS ) { + // rectangular bar + const height = MAX_BAR_SIZE.height * ( numberOfAtoms / MAX_NUMBER_OF_ATOMS ); + barShape = Shape.rect( 0, -height, MAX_BAR_SIZE.width, height ); + } + else { + // bar with upward-pointing arrow, path is specified clockwise from arrow tip. + barShape = new Shape() + .moveTo( 0, -MAX_BAR_SIZE.height ) + .lineTo( ARROW_SIZE.width / 2, -( MAX_BAR_SIZE.height - ARROW_SIZE.height ) ) + .lineTo( MAX_BAR_SIZE.width / 2, -( MAX_BAR_SIZE.height - ARROW_SIZE.height ) ) + .lineTo( MAX_BAR_SIZE.width / 2, 0 ) + .lineTo( -MAX_BAR_SIZE.width / 2, 0 ) + .lineTo( -MAX_BAR_SIZE.width / 2, -( MAX_BAR_SIZE.height - ARROW_SIZE.height ) ) + .lineTo( -ARROW_SIZE.width / 2, -( MAX_BAR_SIZE.height - ARROW_SIZE.height ) ) + .close(); + } + barNode.setShape( barShape ); + barNode.visible = ( numberOfAtoms > 0 ); + + this.bottom = 0; + }; + + // when the number of atoms changes ... + numberOfAtomsProperty.link( numberOfAtomsListener ); + + // @private + this.disposeBarNode = function() { + numberOfAtomsProperty.unlink( numberOfAtomsListener ); + }; + } + + // @public + dispose() { + this.disposeBarNode(); + super.dispose(); } +} - return balancingChemicalEquations.register( 'BarNode', BarNode ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'BarNode', BarNode ); +export default BarNode; \ No newline at end of file diff --git a/js/common/view/BeamNode.js b/js/common/view/BeamNode.js index a6e4f4ce..db03666c 100644 --- a/js/common/view/BeamNode.js +++ b/js/common/view/BeamNode.js @@ -6,37 +6,34 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const merge = require( 'PHET_CORE/merge' ); - const Rectangle = require( 'SCENERY/nodes/Rectangle' ); - - class BeamNode extends Rectangle { - - /** - * @param {number} beamLength - * @param {number} beamThickness - */ - constructor( beamLength, beamThickness, options ) { - - options = merge( { - fill: 'black', - stroke: 'black' - }, options ); - - super( -beamLength / 2, -beamThickness / 2, beamLength, beamThickness, options ); - } - - // @public - setHighlighted( highlighted ) { - this.fill = highlighted ? BCEConstants.BALANCED_HIGHLIGHT_COLOR : 'black'; - this.lineWidth = highlighted ? 1 : 0; - } + +import merge from '../../../../phet-core/js/merge.js'; +import Rectangle from '../../../../scenery/js/nodes/Rectangle.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../BCEConstants.js'; + +class BeamNode extends Rectangle { + + /** + * @param {number} beamLength + * @param {number} beamThickness + */ + constructor( beamLength, beamThickness, options ) { + + options = merge( { + fill: 'black', + stroke: 'black' + }, options ); + + super( -beamLength / 2, -beamThickness / 2, beamLength, beamThickness, options ); + } + + // @public + setHighlighted( highlighted ) { + this.fill = highlighted ? BCEConstants.BALANCED_HIGHLIGHT_COLOR : 'black'; + this.lineWidth = highlighted ? 1 : 0; } +} - return balancingChemicalEquations.register( 'BeamNode', BeamNode ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'BeamNode', BeamNode ); +export default BeamNode; \ No newline at end of file diff --git a/js/common/view/BoxNode.js b/js/common/view/BoxNode.js index ecd24558..697b578c 100644 --- a/js/common/view/BoxNode.js +++ b/js/common/view/BoxNode.js @@ -6,179 +6,176 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - - // modules - const AccordionBox = require( 'SUN/AccordionBox' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Rectangle = require( 'SCENERY/nodes/Rectangle' ); - const Text = require( 'SCENERY/nodes/Text' ); - const Vector2 = require( 'DOT/Vector2' ); - - class BoxNode extends AccordionBox { - - /** - * @param {Property.} equationProperty - * @param {function} getTerms 1 parameter {Equation}, gets the terms that this box will display - * @param {function} getXOffsets 1 parameter {Equation}, gets the x-offsets for each term - * @param {DOT.Range} coefficientRange range of the coefficients - * @param {string} titleString - * @param {Object} [options] - */ - constructor( equationProperty, getTerms, getXOffsets, coefficientRange, titleString, options ) { - - options = merge( { - - boxWidth: 100, - boxHeight: 100, - - // AccordionBox options - titleAlignX: 'center', - resize: false, - fill: 'white', - stroke: 'black', - lineWidth: 1, - cornerRadius: 0, - buttonAlign: 'right', - buttonXMargin: 5, - buttonYMargin: 5, - showTitleWhenExpanded: false, - titleBarExpandCollapse: false, - titleXMargin: 0, - titleXSpacing: 0, - contentXMargin: 0, - contentYMargin: 0, - contentXSpacing: 0, - contentYSpacing: 0, - contentAlign: 'left', - expandCollapseButtonOptions: { - sideLength: 15, - touchAreaXDilation: 20, - touchAreaYDilation: 20, - mouseAreaXDilation: 10, - mouseAreaYDilation: 10 - } - }, options ); - assert && assert( !options.titleNode, 'BoxNode sets titleNode' ); - options.titleNode = new Text( titleString, { - font: new PhetFont( { size: 18, weight: 'bold' } ), - maxWidth: 0.75 * options.boxWidth - } ); +import Vector2 from '../../../../dot/js/Vector2.js'; +import merge from '../../../../phet-core/js/merge.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Rectangle from '../../../../scenery/js/nodes/Rectangle.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import AccordionBox from '../../../../sun/js/AccordionBox.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../BCEConstants.js'; + +class BoxNode extends AccordionBox { + + /** + * @param {Property.} equationProperty + * @param {function} getTerms 1 parameter {Equation}, gets the terms that this box will display + * @param {function} getXOffsets 1 parameter {Equation}, gets the x-offsets for each term + * @param {DOT.Range} coefficientRange range of the coefficients + * @param {string} titleString + * @param {Object} [options] + */ + constructor( equationProperty, getTerms, getXOffsets, coefficientRange, titleString, options ) { + + options = merge( { + + boxWidth: 100, + boxHeight: 100, + + // AccordionBox options + titleAlignX: 'center', + resize: false, + fill: 'white', + stroke: 'black', + lineWidth: 1, + cornerRadius: 0, + buttonAlign: 'right', + buttonXMargin: 5, + buttonYMargin: 5, + showTitleWhenExpanded: false, + titleBarExpandCollapse: false, + titleXMargin: 0, + titleXSpacing: 0, + contentXMargin: 0, + contentYMargin: 0, + contentXSpacing: 0, + contentYSpacing: 0, + contentAlign: 'left', + expandCollapseButtonOptions: { + sideLength: 15, + touchAreaXDilation: 20, + touchAreaYDilation: 20, + mouseAreaXDilation: 10, + mouseAreaYDilation: 10 + } + }, options ); - // Content will be placed to the left of expand/collapse button, so contentWidth is only part of boxWidth. - // See https://github.com/phetsims/balancing-chemical-equations/issues/125 - assert && assert( options.showTitleWhenExpanded === false && options.titleAlignX === 'center', - 'computation of contentWidth is dependent on specific option values' ); - const contentWidth = options.boxWidth - options.expandCollapseButtonOptions.sideLength - options.buttonXMargin; + assert && assert( !options.titleNode, 'BoxNode sets titleNode' ); + options.titleNode = new Text( titleString, { + font: new PhetFont( { size: 18, weight: 'bold' } ), + maxWidth: 0.75 * options.boxWidth + } ); - // constant-sized rectangle - const contentNode = new Rectangle( 0, 0, contentWidth, options.boxHeight, { + // Content will be placed to the left of expand/collapse button, so contentWidth is only part of boxWidth. + // See https://github.com/phetsims/balancing-chemical-equations/issues/125 + assert && assert( options.showTitleWhenExpanded === false && options.titleAlignX === 'center', + 'computation of contentWidth is dependent on specific option values' ); + const contentWidth = options.boxWidth - options.expandCollapseButtonOptions.sideLength - options.buttonXMargin; - // With ?dev query parameter, put a red stroke around the content, for debugging layout of #125 - stroke: phet.chipper.queryParameters.dev ? 'red' : null - } ); + // constant-sized rectangle + const contentNode = new Rectangle( 0, 0, contentWidth, options.boxHeight, { - // @private parent for all molecule nodes - const moleculesParent = new Node(); - contentNode.addChild( moleculesParent ); + // With ?dev query parameter, put a red stroke around the content, for debugging layout of #125 + stroke: phet.chipper.queryParameters.dev ? 'red' : null + } ); - super( contentNode, options ); + // @private parent for all molecule nodes + const moleculesParent = new Node(); + contentNode.addChild( moleculesParent ); - // @private - this.boxHeight = options.boxHeight; - this.coefficientRange = coefficientRange; // @private - this.termNodes = {}; // @private molecule nodes for each term, key: term.molecule.symbol, value: [{Node}] - this.moleculesParent = moleculesParent; + super( contentNode, options ); - // update visible molecules to match the coefficients - const coefficientsObserver = () => { - this.updateCounts( getTerms( equationProperty.get() ), getXOffsets( equationProperty.get() ) ); - }; + // @private + this.boxHeight = options.boxHeight; + this.coefficientRange = coefficientRange; // @private + this.termNodes = {}; // @private molecule nodes for each term, key: term.molecule.symbol, value: [{Node}] + this.moleculesParent = moleculesParent; - equationProperty.link( ( newEquation, oldEquation ) => { + // update visible molecules to match the coefficients + const coefficientsObserver = () => { + this.updateCounts( getTerms( equationProperty.get() ), getXOffsets( equationProperty.get() ) ); + }; - // updates the node for molecules of the current equation - this.updateNode( getTerms( newEquation ), getXOffsets( newEquation ) ); + equationProperty.link( ( newEquation, oldEquation ) => { - // wire up coefficients observer to current equation - if ( oldEquation ) { - oldEquation.removeCoefficientsObserver( coefficientsObserver ); - } - newEquation.addCoefficientsObserver( coefficientsObserver ); - } ); - } + // updates the node for molecules of the current equation + this.updateNode( getTerms( newEquation ), getXOffsets( newEquation ) ); - // No dispose needed, instances of this type persist for lifetime of the sim. - - /** - * Creates molecules in the boxes for one set of terms (reactants or products). - * To improve performance: - * - Molecules are created as needed. - * - Molecules are never removed; they remain as children for the lifetime of this node. - * - The visibility of molecules is adjusted to show the correct number of molecules. - * - * @param {EquationTerm} terms array - * @param {number[]} xOffsets array of offsets for terms - * @private - */ - updateNode( terms, xOffsets ) { - - // remove all molecule nodes - this.moleculesParent.removeAllChildren(); - - // clear the map - this.termNodes = {}; - for ( let i = 0; i < terms.length; i++ ) { - this.termNodes[ terms[ i ].molecule.symbol ] = []; + // wire up coefficients observer to current equation + if ( oldEquation ) { + oldEquation.removeCoefficientsObserver( coefficientsObserver ); } + newEquation.addCoefficientsObserver( coefficientsObserver ); + } ); + } - this.updateCounts( terms, xOffsets ); + // No dispose needed, instances of this type persist for lifetime of the sim. + + /** + * Creates molecules in the boxes for one set of terms (reactants or products). + * To improve performance: + * - Molecules are created as needed. + * - Molecules are never removed; they remain as children for the lifetime of this node. + * - The visibility of molecules is adjusted to show the correct number of molecules. + * + * @param {EquationTerm} terms array + * @param {number[]} xOffsets array of offsets for terms + * @private + */ + updateNode( terms, xOffsets ) { + + // remove all molecule nodes + this.moleculesParent.removeAllChildren(); + + // clear the map + this.termNodes = {}; + for ( let i = 0; i < terms.length; i++ ) { + this.termNodes[ terms[ i ].molecule.symbol ] = []; } - /** - * Updates visibility of molecules to match the current coefficients. - * - * @param {EquationTerm[]} terms - * @param {number[]} xOffsets array of offsets for terms - * @private - */ - updateCounts( terms, xOffsets ) { - - const Y_MARGIN = 0; - const rowHeight = ( this.boxHeight - ( 2 * Y_MARGIN ) ) / this.coefficientRange.max; - - for ( let i = 0; i < terms.length; i++ ) { - - const moleculeNodes = this.termNodes[ terms[ i ].molecule.symbol ]; - const userCoefficient = terms[ i ].userCoefficientProperty.get(); - const MoleculeNodeConstructor = terms[ i ].molecule.nodeConstructor; - let y = this.boxHeight - Y_MARGIN - ( rowHeight / 2 ); - - for ( let j = 0; j < Math.max( userCoefficient, moleculeNodes.length ); j++ ) { - if ( j < moleculeNodes.length ) { - // set visibility of a molecule that already exists - moleculeNodes[ j ].visible = ( j < userCoefficient ); - } - else { - // add a molecule node - const moleculeNode = new MoleculeNodeConstructor( { atomOptions: BCEConstants.ATOM_OPTIONS } ); - moleculeNode.scale( BCEConstants.MOLECULE_SCALE_FACTOR ); - this.moleculesParent.addChild( moleculeNode ); - moleculeNode.center = new Vector2( xOffsets[ i ] - this.x, y ); - - moleculeNodes.push( moleculeNode ); - } - y -= rowHeight; + this.updateCounts( terms, xOffsets ); + } + + /** + * Updates visibility of molecules to match the current coefficients. + * + * @param {EquationTerm[]} terms + * @param {number[]} xOffsets array of offsets for terms + * @private + */ + updateCounts( terms, xOffsets ) { + + const Y_MARGIN = 0; + const rowHeight = ( this.boxHeight - ( 2 * Y_MARGIN ) ) / this.coefficientRange.max; + + for ( let i = 0; i < terms.length; i++ ) { + + const moleculeNodes = this.termNodes[ terms[ i ].molecule.symbol ]; + const userCoefficient = terms[ i ].userCoefficientProperty.get(); + const MoleculeNodeConstructor = terms[ i ].molecule.nodeConstructor; + let y = this.boxHeight - Y_MARGIN - ( rowHeight / 2 ); + + for ( let j = 0; j < Math.max( userCoefficient, moleculeNodes.length ); j++ ) { + if ( j < moleculeNodes.length ) { + // set visibility of a molecule that already exists + moleculeNodes[ j ].visible = ( j < userCoefficient ); + } + else { + // add a molecule node + const moleculeNode = new MoleculeNodeConstructor( { atomOptions: BCEConstants.ATOM_OPTIONS } ); + moleculeNode.scale( BCEConstants.MOLECULE_SCALE_FACTOR ); + this.moleculesParent.addChild( moleculeNode ); + moleculeNode.center = new Vector2( xOffsets[ i ] - this.x, y ); + + moleculeNodes.push( moleculeNode ); } + y -= rowHeight; } } } +} - return balancingChemicalEquations.register( 'BoxNode', BoxNode ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'BoxNode', BoxNode ); +export default BoxNode; \ No newline at end of file diff --git a/js/common/view/BoxesNode.js b/js/common/view/BoxesNode.js index 6e25044a..c3dfef35 100644 --- a/js/common/view/BoxesNode.js +++ b/js/common/view/BoxesNode.js @@ -7,88 +7,85 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BoxNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BoxNode' ); - const Node = require( 'SCENERY/nodes/Node' ); - const RightArrowNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/RightArrowNode' ); - const Vector2 = require( 'DOT/Vector2' ); +import Vector2 from '../../../../dot/js/Vector2.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import balancingChemicalEquationsStrings from '../../balancing-chemical-equations-strings.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BoxNode from './BoxNode.js'; +import RightArrowNode from './RightArrowNode.js'; - // strings - const productsString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/products' ); - const reactantsString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/reactants' ); +const productsString = balancingChemicalEquationsStrings.products; +const reactantsString = balancingChemicalEquationsStrings.reactants; - class BoxesNode extends Node { +class BoxesNode extends Node { - /** - * @param {Property.} equationProperty the equation displayed in the boxes - * @param {Range} coefficientsRange - * @param {HorizontalAligner} aligner provides layout information to ensure horizontal alignment with other user-interface elements - * @param {Dimension2} boxSize - * @param {string} boxColor fill color of the boxes - * @param {Property.} reactantsBoxExpandedProperty - * @param {Property.} productsBoxExpandedProperty - * @param {Object} [options] - */ - constructor( equationProperty, coefficientsRange, aligner, boxSize, boxColor, - reactantsBoxExpandedProperty, productsBoxExpandedProperty, options ) { + /** + * @param {Property.} equationProperty the equation displayed in the boxes + * @param {Range} coefficientsRange + * @param {HorizontalAligner} aligner provides layout information to ensure horizontal alignment with other user-interface elements + * @param {Dimension2} boxSize + * @param {string} boxColor fill color of the boxes + * @param {Property.} reactantsBoxExpandedProperty + * @param {Property.} productsBoxExpandedProperty + * @param {Object} [options] + */ + constructor( equationProperty, coefficientsRange, aligner, boxSize, boxColor, + reactantsBoxExpandedProperty, productsBoxExpandedProperty, options ) { - // reactants box, on the left - const reactantsBoxNode = new BoxNode( equationProperty, - function( equation ) { return equation.reactants; }, - function( equation ) { return aligner.getReactantXOffsets( equation ); }, - coefficientsRange, - reactantsString, { - expandedProperty: reactantsBoxExpandedProperty, - fill: boxColor, - boxWidth: boxSize.width, - boxHeight: boxSize.height, - x: aligner.getReactantsBoxLeft(), - y: 0 - } ); - - // products box, on the right - const productsBoxNode = new BoxNode( equationProperty, - function( equation ) { return equation.products; }, - function( equation ) { return aligner.getProductXOffsets( equation ); }, - coefficientsRange, - productsString, { - expandedProperty: productsBoxExpandedProperty, - fill: boxColor, - boxWidth: boxSize.width, - boxHeight: boxSize.height, - x: aligner.getProductsBoxLeft(), - y: 0 - } ); + // reactants box, on the left + const reactantsBoxNode = new BoxNode( equationProperty, + function( equation ) { return equation.reactants; }, + function( equation ) { return aligner.getReactantXOffsets( equation ); }, + coefficientsRange, + reactantsString, { + expandedProperty: reactantsBoxExpandedProperty, + fill: boxColor, + boxWidth: boxSize.width, + boxHeight: boxSize.height, + x: aligner.getReactantsBoxLeft(), + y: 0 + } ); - // right-pointing arrow, in the middle - const arrowNode = new RightArrowNode( equationProperty, { - center: new Vector2( aligner.getScreenCenterX(), boxSize.height / 2 ) + // products box, on the right + const productsBoxNode = new BoxNode( equationProperty, + function( equation ) { return equation.products; }, + function( equation ) { return aligner.getProductXOffsets( equation ); }, + coefficientsRange, + productsString, { + expandedProperty: productsBoxExpandedProperty, + fill: boxColor, + boxWidth: boxSize.width, + boxHeight: boxSize.height, + x: aligner.getProductsBoxLeft(), + y: 0 } ); - options.children = [ reactantsBoxNode, productsBoxNode, arrowNode ]; - super( options ); + // right-pointing arrow, in the middle + const arrowNode = new RightArrowNode( equationProperty, { + center: new Vector2( aligner.getScreenCenterX(), boxSize.height / 2 ) + } ); - // private - this.arrowNode = arrowNode; - } + options.children = [ reactantsBoxNode, productsBoxNode, arrowNode ]; + super( options ); + + // private + this.arrowNode = arrowNode; + } - // No dispose needed, instances of this type persist for lifetime of the sim. + // No dispose needed, instances of this type persist for lifetime of the sim. - /** - * Enables or disables the highlighting feature. - * When enabled, the arrow between the boxes will light up when the equation is balanced. - * This is enabled by default, but we want to disable in the Game until the user presses the "Check" button. - * @param enabled - * @public - */ - setBalancedHighlightEnabled( enabled ) { - this.arrowNode.highlightEnabled = enabled; - } + /** + * Enables or disables the highlighting feature. + * When enabled, the arrow between the boxes will light up when the equation is balanced. + * This is enabled by default, but we want to disable in the Game until the user presses the "Check" button. + * @param enabled + * @public + */ + setBalancedHighlightEnabled( enabled ) { + this.arrowNode.highlightEnabled = enabled; } +} - return balancingChemicalEquations.register( 'BoxesNode', BoxesNode ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'BoxesNode', BoxesNode ); +export default BoxesNode; \ No newline at end of file diff --git a/js/common/view/EqualityOperatorNode.js b/js/common/view/EqualityOperatorNode.js index a068f43d..869631c9 100644 --- a/js/common/view/EqualityOperatorNode.js +++ b/js/common/view/EqualityOperatorNode.js @@ -5,54 +5,51 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Text = require( 'SCENERY/nodes/Text' ); - - class EqualityOperatorNode extends Node { - - /** - * @param {Property.} equationProperty - * @param {Object} [options] - */ - constructor( equationProperty, options ) { - - options = merge( {}, options ); - - const textOptions = { - font: new PhetFont( 80 ), - stroke: 'black' - }; - - // nodes - const equalsSignNode = new Text( '\u003D', - merge( { fill: BCEConstants.BALANCED_HIGHLIGHT_COLOR }, textOptions ) ); - const notEqualsSignNode = new Text( '\u2260', - merge( { fill: BCEConstants.UNBALANCED_COLOR, center: equalsSignNode.center }, textOptions ) ); - - options.children = [ equalsSignNode, notEqualsSignNode ]; - super( options ); - - // show the correct operator, based on whether the equation is balanced - const balancedObserver = function( balanced ) { - equalsSignNode.visible = balanced; - notEqualsSignNode.visible = !balanced; - }; - equationProperty.link( function( newEquation, oldEquation ) { - if ( oldEquation ) { oldEquation.balancedProperty.unlink( balancedObserver ); } - newEquation.balancedProperty.link( balancedObserver ); - } ); - } - - // No dispose needed, instances of this type persist for lifetime of the sim. + +import merge from '../../../../phet-core/js/merge.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../BCEConstants.js'; + +class EqualityOperatorNode extends Node { + + /** + * @param {Property.} equationProperty + * @param {Object} [options] + */ + constructor( equationProperty, options ) { + + options = merge( {}, options ); + + const textOptions = { + font: new PhetFont( 80 ), + stroke: 'black' + }; + + // nodes + const equalsSignNode = new Text( '\u003D', + merge( { fill: BCEConstants.BALANCED_HIGHLIGHT_COLOR }, textOptions ) ); + const notEqualsSignNode = new Text( '\u2260', + merge( { fill: BCEConstants.UNBALANCED_COLOR, center: equalsSignNode.center }, textOptions ) ); + + options.children = [ equalsSignNode, notEqualsSignNode ]; + super( options ); + + // show the correct operator, based on whether the equation is balanced + const balancedObserver = function( balanced ) { + equalsSignNode.visible = balanced; + notEqualsSignNode.visible = !balanced; + }; + equationProperty.link( function( newEquation, oldEquation ) { + if ( oldEquation ) { oldEquation.balancedProperty.unlink( balancedObserver ); } + newEquation.balancedProperty.link( balancedObserver ); + } ); } - return balancingChemicalEquations.register( 'EqualityOperatorNode', EqualityOperatorNode ); -} ); + // No dispose needed, instances of this type persist for lifetime of the sim. +} + +balancingChemicalEquations.register( 'EqualityOperatorNode', EqualityOperatorNode ); +export default EqualityOperatorNode; \ No newline at end of file diff --git a/js/common/view/EquationNode.js b/js/common/view/EquationNode.js index 94ef542d..7a74127f 100644 --- a/js/common/view/EquationNode.js +++ b/js/common/view/EquationNode.js @@ -6,165 +6,161 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const PlusNode = require( 'SCENERY_PHET/PlusNode' ); - const RightArrowNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/RightArrowNode' ); - const TermNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/TermNode' ); - const Vector2 = require( 'DOT/Vector2' ); - - class EquationNode extends Node { - /** - * @param {Property.} equationProperty - * @param {DOT.Range} coefficientRange range of the coefficients - * @param {HorizontalAligner} aligner provides layout information to ensure horizontal alignment with other user-interface elements - * @param {Object} [options] - */ - constructor( equationProperty, coefficientRange, aligner, options ) { - - options = merge( { fontSize: 32 }, options ); - - super(); - - this.fontSize = options.fontSize; // @private - this.coefficientRange = coefficientRange; // @private - this.balancedHighlightEnabled = true; // @private - this.aligner = aligner; // @private - this.equationProperty = equationProperty; // @private - - // @private arrow node, at a fixed position - this.arrowNode = new RightArrowNode( equationProperty, { centerX: this.aligner.getScreenCenterX() } ); // @private - this.addChild( this.arrowNode ); - - this.terms = []; // @private the set of TermNodes in the equation - - this.termsParent = new Node(); // @private the parent for all terms and the "+" operators - this.addChild( this.termsParent ); - - // if the equation changes... - equationProperty.link( ( newEquation, oldEquation ) => this.updateNode() ); - - this.mutate( options ); - } - // No dispose needed, instances of this type persist for lifetime of the sim. +import Vector2 from '../../../../dot/js/Vector2.js'; +import merge from '../../../../phet-core/js/merge.js'; +import PlusNode from '../../../../scenery-phet/js/PlusNode.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import RightArrowNode from './RightArrowNode.js'; +import TermNode from './TermNode.js'; - /** - * Rebuilds the left and right sides of the equation. - * @private - */ - updateNode() { +class EquationNode extends Node { + /** + * @param {Property.} equationProperty + * @param {DOT.Range} coefficientRange range of the coefficients + * @param {HorizontalAligner} aligner provides layout information to ensure horizontal alignment with other user-interface elements + * @param {Object} [options] + */ + constructor( equationProperty, coefficientRange, aligner, options ) { - // dispose of existing instances of TermNode - this.terms.forEach( termNode => termNode.dispose() ); - this.terms.length = 0; - this.termsParent.removeAllChildren(); + options = merge( { fontSize: 32 }, options ); - const equation = this.equationProperty.get(); - this.updateSideOfEquation( equation.reactants, this.aligner.getReactantXOffsets( equation ), - this.aligner.getReactantsBoxLeft(), this.aligner.getReactantsBoxRight() ); - this.updateSideOfEquation( equation.products, this.aligner.getProductXOffsets( equation ), - this.aligner.getProductsBoxLeft(), this.aligner.getScreenRight() ); - } + super(); - /** - * Rebuilds one side of the equation. - * - * @param {EquationTerm} terms array - * @param {number} xOffsets array for terms - * @param {number} minX minimal possible x for equation - * @param {number} maxX maximum possible x for equation - * @private - */ - updateSideOfEquation( terms, xOffsets, minX, maxX ) { - - let plusNode; - let termNode; - const minSeparation = 15; - const tempNodes = []; // contains all nodes for position adjustment if needed - - for ( var i = 0; i < terms.length; i++ ) { - // term - termNode = new TermNode( this.coefficientRange, terms[ i ], { fontSize: this.fontSize } ); - this.terms.push( termNode ); - this.termsParent.addChild( termNode ); - termNode.center = new Vector2( xOffsets[ i ], 0 ); - - // if node over previous plusNode move node to the right - if ( i > 0 ) { - if ( termNode.bounds.minX - minSeparation < tempNodes[ tempNodes.length - 1 ].bounds.maxX ) { - termNode.x += tempNodes[ tempNodes.length - 1 ].bounds.maxX - (termNode.bounds.minX - minSeparation); - } - } - tempNodes.push( termNode ); - - if ( terms.length > 1 && i < terms.length - 1 ) { - plusNode = new PlusNode(); - this.termsParent.addChild( plusNode ); - plusNode.centerX = xOffsets[ i ] + ( ( xOffsets[ i + 1 ] - xOffsets[ i ] ) / 2 ); // centered between 2 offsets; - plusNode.centerY = termNode.centerY; - tempNodes.push( plusNode ); - - // if previous node over plusNode move node to the left - if ( termNode.bounds.maxX + minSeparation > plusNode.bounds.minX ) { - termNode.x = termNode.x - (termNode.bounds.maxX + minSeparation - plusNode.bounds.minX); - } - } - } + this.fontSize = options.fontSize; // @private + this.coefficientRange = coefficientRange; // @private + this.balancedHighlightEnabled = true; // @private + this.aligner = aligner; // @private + this.equationProperty = equationProperty; // @private - let dx; - // check if equation fits minX (eg, C2H5OH + 3O2 -> 2CO2 + 3H2O) - if ( tempNodes[ 0 ].bounds.minX < minX ) { // adjust all terms to the right - let rightBound = minX; // current right bound of passed terms, if term.minX this.updateNode() ); + + this.mutate( options ); + } + + // No dispose needed, instances of this type persist for lifetime of the sim. + + /** + * Rebuilds the left and right sides of the equation. + * @private + */ + updateNode() { - // check if equation fits maxX (eg, CH3OH -> CO + 2H2) - if ( tempNodes[ tempNodes.length - 1 ].bounds.maxX > maxX ) { // adjust all terms to the left - let leftBound = maxX; // current left bound of passed terms, if term.maxX > leftBound, move term to the left - for ( i = tempNodes[ tempNodes.length - 1 ]; i > -1; i-- ) { - const term = tempNodes[ i ]; - dx = Math.max( 0, term.bounds.maxX - leftBound ); - term.x -= dx; - leftBound = term.bounds.minX - minSeparation; + // dispose of existing instances of TermNode + this.terms.forEach( termNode => termNode.dispose() ); + this.terms.length = 0; + this.termsParent.removeAllChildren(); + + const equation = this.equationProperty.get(); + this.updateSideOfEquation( equation.reactants, this.aligner.getReactantXOffsets( equation ), + this.aligner.getReactantsBoxLeft(), this.aligner.getReactantsBoxRight() ); + this.updateSideOfEquation( equation.products, this.aligner.getProductXOffsets( equation ), + this.aligner.getProductsBoxLeft(), this.aligner.getScreenRight() ); + } + + /** + * Rebuilds one side of the equation. + * + * @param {EquationTerm} terms array + * @param {number} xOffsets array for terms + * @param {number} minX minimal possible x for equation + * @param {number} maxX maximum possible x for equation + * @private + */ + updateSideOfEquation( terms, xOffsets, minX, maxX ) { + + let plusNode; + let termNode; + const minSeparation = 15; + const tempNodes = []; // contains all nodes for position adjustment if needed + + for ( var i = 0; i < terms.length; i++ ) { + // term + termNode = new TermNode( this.coefficientRange, terms[ i ], { fontSize: this.fontSize } ); + this.terms.push( termNode ); + this.termsParent.addChild( termNode ); + termNode.center = new Vector2( xOffsets[ i ], 0 ); + + // if node over previous plusNode move node to the right + if ( i > 0 ) { + if ( termNode.bounds.minX - minSeparation < tempNodes[ tempNodes.length - 1 ].bounds.maxX ) { + termNode.x += tempNodes[ tempNodes.length - 1 ].bounds.maxX - ( termNode.bounds.minX - minSeparation ); + } + } + tempNodes.push( termNode ); + + if ( terms.length > 1 && i < terms.length - 1 ) { + plusNode = new PlusNode(); + this.termsParent.addChild( plusNode ); + plusNode.centerX = xOffsets[ i ] + ( ( xOffsets[ i + 1 ] - xOffsets[ i ] ) / 2 ); // centered between 2 offsets; + plusNode.centerY = termNode.centerY; + tempNodes.push( plusNode ); + + // if previous node over plusNode move node to the left + if ( termNode.bounds.maxX + minSeparation > plusNode.bounds.minX ) { + termNode.x = termNode.x - ( termNode.bounds.maxX + minSeparation - plusNode.bounds.minX ); } } - - this.arrowNode.centerY = termNode.centerY; } - /** - * Enables or disables the highlighting feature. - * When enabled, the arrow between the left and right sides of the equation will light up when the equation is balanced. - * This is enabled by default, but we want to disable in the Game until the user presses the "Check" button. - * - * @param enabled - * @public - */ - setBalancedHighlightEnabled( enabled ) { - this.arrowNode.highlightEnabled = enabled; + let dx; + // check if equation fits minX (eg, C2H5OH + 3O2 -> 2CO2 + 3H2O) + if ( tempNodes[ 0 ].bounds.minX < minX ) { // adjust all terms to the right + let rightBound = minX; // current right bound of passed terms, if term.minX CO + 2H2) + if ( tempNodes[ tempNodes.length - 1 ].bounds.maxX > maxX ) { // adjust all terms to the left + let leftBound = maxX; // current left bound of passed terms, if term.maxX > leftBound, move term to the left + for ( i = tempNodes[ tempNodes.length - 1 ]; i > -1; i-- ) { + const term = tempNodes[ i ]; + dx = Math.max( 0, term.bounds.maxX - leftBound ); + term.x -= dx; + leftBound = term.bounds.minX - minSeparation; } } + + this.arrowNode.centerY = termNode.centerY; + } + + /** + * Enables or disables the highlighting feature. + * When enabled, the arrow between the left and right sides of the equation will light up when the equation is balanced. + * This is enabled by default, but we want to disable in the Game until the user presses the "Check" button. + * + * @param enabled + * @public + */ + setBalancedHighlightEnabled( enabled ) { + this.arrowNode.highlightEnabled = enabled; + } + + /** + * Enables or disabled each TermNode in the equation. + * @param {boolean} enabled + * @public + */ + setEnabled( enabled ) { + for ( let i = 0; i < this.terms.length; i++ ) { + this.terms[ i ].setEnabled( enabled ); + } } +} - return balancingChemicalEquations.register( 'EquationNode', EquationNode ); -} ); +export default balancingChemicalEquations.register( 'EquationNode', EquationNode ); \ No newline at end of file diff --git a/js/common/view/FulcrumNode.js b/js/common/view/FulcrumNode.js index 2c757c66..b4559c74 100644 --- a/js/common/view/FulcrumNode.js +++ b/js/common/view/FulcrumNode.js @@ -6,51 +6,48 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const LinearGradient = require( 'SCENERY/util/LinearGradient' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const Path = require( 'SCENERY/nodes/Path' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Shape = require( 'KITE/Shape' ); - const Text = require( 'SCENERY/nodes/Text' ); - - class FulcrumNode extends Node { - - /** - * @param {NITROGLYCERIN.Element} element to show - * @param {DOT.Dimension2} fulcrumSize width and height - * @param {Object} [options] - */ - constructor( element, fulcrumSize, options ) { - - options = merge( { - fill: new LinearGradient( 0, 0, 0, fulcrumSize.height ).addColorStop( 0, 'white' ).addColorStop( 1, 'rgb(192, 192, 192)' ), - font: new PhetFont( 22 ) - }, options ); - - // triangle, start at tip and move clockwise - const triangleNode = new Path( new Shape() - .moveTo( 0, 0 ) - .lineTo( fulcrumSize.width / 2, fulcrumSize.height ) - .lineTo( -fulcrumSize.width / 2, fulcrumSize.height ) - .close(), - { fill: options.fill, lineWidth: 1, stroke: 'black' } - ); - - // atom symbol, centered in triangle - const symbolNode = new Text( element.symbol, - { font: options.font, centerX: triangleNode.centerX, centerY: triangleNode.centerY + 8 } - ); - - options.children = [ triangleNode, symbolNode ]; - super( options ); - } + +import Shape from '../../../../kite/js/Shape.js'; +import merge from '../../../../phet-core/js/merge.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Path from '../../../../scenery/js/nodes/Path.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import LinearGradient from '../../../../scenery/js/util/LinearGradient.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; + +class FulcrumNode extends Node { + + /** + * @param {NITROGLYCERIN.Element} element to show + * @param {DOT.Dimension2} fulcrumSize width and height + * @param {Object} [options] + */ + constructor( element, fulcrumSize, options ) { + + options = merge( { + fill: new LinearGradient( 0, 0, 0, fulcrumSize.height ).addColorStop( 0, 'white' ).addColorStop( 1, 'rgb(192, 192, 192)' ), + font: new PhetFont( 22 ) + }, options ); + + // triangle, start at tip and move clockwise + const triangleNode = new Path( new Shape() + .moveTo( 0, 0 ) + .lineTo( fulcrumSize.width / 2, fulcrumSize.height ) + .lineTo( -fulcrumSize.width / 2, fulcrumSize.height ) + .close(), + { fill: options.fill, lineWidth: 1, stroke: 'black' } + ); + + // atom symbol, centered in triangle + const symbolNode = new Text( element.symbol, + { font: options.font, centerX: triangleNode.centerX, centerY: triangleNode.centerY + 8 } + ); + + options.children = [ triangleNode, symbolNode ]; + super( options ); } +} - return balancingChemicalEquations.register( 'FulcrumNode', FulcrumNode ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'FulcrumNode', FulcrumNode ); +export default FulcrumNode; \ No newline at end of file diff --git a/js/common/view/HorizontalAligner.js b/js/common/view/HorizontalAligner.js index f6ae69e8..47c172f9 100644 --- a/js/common/view/HorizontalAligner.js +++ b/js/common/view/HorizontalAligner.js @@ -10,131 +10,128 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; - class HorizontalAligner { +class HorizontalAligner { - /** - * @param {number} screenWidth screen width - * @param {DOT.Dimension2} boxWidth size of one of the 2 boxes (both boxes are assumed to be the same size) - * @param {number} boxXSpacing horizontal separation between the left and right boxes - */ - constructor( screenWidth, boxWidth, boxXSpacing ) { - this.screenWidth = screenWidth; // @private - this.boxWidth = boxWidth; // @private - this.boxXSpacing = boxXSpacing; // @private - } + /** + * @param {number} screenWidth screen width + * @param {DOT.Dimension2} boxWidth size of one of the 2 boxes (both boxes are assumed to be the same size) + * @param {number} boxXSpacing horizontal separation between the left and right boxes + */ + constructor( screenWidth, boxWidth, boxXSpacing ) { + this.screenWidth = screenWidth; // @private + this.boxWidth = boxWidth; // @private + this.boxXSpacing = boxXSpacing; // @private + } - /** - * Gets the offsets for an equation's reactant terms. - * Reactants are on the left-hand side of the equation. - * @param equation - * @public - */ - getReactantXOffsets( equation ) { - const boxLeft = this.screenWidth / 2 - this.boxWidth - this.boxXSpacing / 2; - return getXOffsets( equation.reactants, this.boxWidth, boxLeft, 'right' ); - } + /** + * Gets the offsets for an equation's reactant terms. + * Reactants are on the left-hand side of the equation. + * @param equation + * @public + */ + getReactantXOffsets( equation ) { + const boxLeft = this.screenWidth / 2 - this.boxWidth - this.boxXSpacing / 2; + return getXOffsets( equation.reactants, this.boxWidth, boxLeft, 'right' ); + } - /** - * Gets the offsets for an equation's product terms. - * Products are on the right-hand side of the equation. - * @param equation - * @public - */ - getProductXOffsets( equation ) { - const boxLeft = this.screenWidth / 2 + this.boxXSpacing / 2; - return getXOffsets( equation.products, this.boxWidth, boxLeft, 'left' ); - } + /** + * Gets the offsets for an equation's product terms. + * Products are on the right-hand side of the equation. + * @param equation + * @public + */ + getProductXOffsets( equation ) { + const boxLeft = this.screenWidth / 2 + this.boxXSpacing / 2; + return getXOffsets( equation.products, this.boxWidth, boxLeft, 'left' ); + } - // @public - getScreenWidth() { return this.screenWidth; } + // @public + getScreenWidth() { return this.screenWidth; } - // @public - getScreenLeft() { return 0; } + // @public + getScreenLeft() { return 0; } - // @public - getScreenRight() { return this.screenWidth; } + // @public + getScreenRight() { return this.screenWidth; } - // @public - getScreenCenterX() { return this.screenWidth / 2; } + // @public + getScreenCenterX() { return this.screenWidth / 2; } - // @public - getReactantsBoxLeft() { - return this.getScreenCenterX() - this.boxXSpacing / 2 - this.boxWidth; - } + // @public + getReactantsBoxLeft() { + return this.getScreenCenterX() - this.boxXSpacing / 2 - this.boxWidth; + } - // @public - getProductsBoxLeft() { - return this.getScreenCenterX() + this.boxXSpacing / 2; - } + // @public + getProductsBoxLeft() { + return this.getScreenCenterX() + this.boxXSpacing / 2; + } - // @public - getReactantsBoxRight() { - return this.getScreenCenterX() - this.boxXSpacing / 2; - } + // @public + getReactantsBoxRight() { + return this.getScreenCenterX() - this.boxXSpacing / 2; + } - // @public - getProductsBoxRight() { - return this.getScreenCenterX() + this.boxXSpacing / 2 + this.boxWidth; - } + // @public + getProductsBoxRight() { + return this.getScreenCenterX() + this.boxXSpacing / 2 + this.boxWidth; + } - // @public - getReactantsBoxCenterX() { - return this.getScreenCenterX() - this.boxXSpacing / 2 - this.boxWidth / 2; - } + // @public + getReactantsBoxCenterX() { + return this.getScreenCenterX() - this.boxXSpacing / 2 - this.boxWidth / 2; + } - // @public - getProductsBoxCenterX() { - return this.getScreenCenterX() + this.boxXSpacing / 2 + this.boxWidth / 2; - } + // @public + getProductsBoxCenterX() { + return this.getScreenCenterX() + this.boxXSpacing / 2 + this.boxWidth / 2; } +} - /** - * Gets the x offsets for a set of terms. - * The box is divided up into columns and terms are centered in the columns. - * @param terms - * @param {number} boxWidth - * @param {number} boxLeft left edge of box - * @param alignment alignment for single term, 'left' or 'right' - * @returns [{number}] x offset for each term - */ - function getXOffsets( terms, boxWidth, boxLeft, alignment ) { - - assert && assert( alignment === 'left' || alignment === 'right' ); - - const numberOfTerms = terms.length; - const xOffsets = []; - if ( numberOfTerms === 1 ) { - /* - * In the special case of 1 term, the box is divided into 2 columns, - * and the single term is centered in the column that corresponds to alignment. - */ - if ( alignment === 'left' ) { - xOffsets[ 0 ] = boxLeft + ( 0.25 * boxWidth ); - } - else { - xOffsets[ 0 ] = boxLeft + ( 0.75 * boxWidth ); - } +/** + * Gets the x offsets for a set of terms. + * The box is divided up into columns and terms are centered in the columns. + * @param terms + * @param {number} boxWidth + * @param {number} boxLeft left edge of box + * @param alignment alignment for single term, 'left' or 'right' + * @returns [{number}] x offset for each term + */ +function getXOffsets( terms, boxWidth, boxLeft, alignment ) { + + assert && assert( alignment === 'left' || alignment === 'right' ); + + const numberOfTerms = terms.length; + const xOffsets = []; + if ( numberOfTerms === 1 ) { + /* + * In the special case of 1 term, the box is divided into 2 columns, + * and the single term is centered in the column that corresponds to alignment. + */ + if ( alignment === 'left' ) { + xOffsets[ 0 ] = boxLeft + ( 0.25 * boxWidth ); } else { - /* - * In the general case of N terms, the box is divided into N columns, - * and one term is centered in each column. - */ - const columnWidth = boxWidth / numberOfTerms; - let x = boxLeft + columnWidth / 2; - for ( let i = 0; i < numberOfTerms; i++ ) { - xOffsets[ i ] = x; - x += columnWidth; - } + xOffsets[ 0 ] = boxLeft + ( 0.75 * boxWidth ); + } + } + else { + /* + * In the general case of N terms, the box is divided into N columns, + * and one term is centered in each column. + */ + const columnWidth = boxWidth / numberOfTerms; + let x = boxLeft + columnWidth / 2; + for ( let i = 0; i < numberOfTerms; i++ ) { + xOffsets[ i ] = x; + x += columnWidth; } - return xOffsets; } + return xOffsets; +} - return balancingChemicalEquations.register( 'HorizontalAligner', HorizontalAligner ); -} ); +balancingChemicalEquations.register( 'HorizontalAligner', HorizontalAligner ); +export default HorizontalAligner; \ No newline at end of file diff --git a/js/common/view/RightArrowNode.js b/js/common/view/RightArrowNode.js index 887a8f8f..a1a620b8 100644 --- a/js/common/view/RightArrowNode.js +++ b/js/common/view/RightArrowNode.js @@ -7,62 +7,59 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const ArrowNode = require( 'SCENERY_PHET/ArrowNode' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const merge = require( 'PHET_CORE/merge' ); +import merge from '../../../../phet-core/js/merge.js'; +import ArrowNode from '../../../../scenery-phet/js/ArrowNode.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../BCEConstants.js'; - // constants - const ARROW_LENGTH = 70; +// constants +const ARROW_LENGTH = 70; - class RightArrowNode extends ArrowNode { +class RightArrowNode extends ArrowNode { - /** - * @param {Property.} equationProperty - * @param {Object} [options] - */ - constructor( equationProperty, options ) { + /** + * @param {Property.} equationProperty + * @param {Object} [options] + */ + constructor( equationProperty, options ) { - options = merge( { - tailWidth: 15, - headWidth: 35, - headHeight: 30 - }, options ); + options = merge( { + tailWidth: 15, + headWidth: 35, + headHeight: 30 + }, options ); - super( 0, 0, ARROW_LENGTH, 0, options ); + super( 0, 0, ARROW_LENGTH, 0, options ); - this.equationProperty = equationProperty; // @private - this._highlightEnabled = true; // @private + this.equationProperty = equationProperty; // @private + this._highlightEnabled = true; // @private - // Wire observer to current equation. - const balancedObserver = this.updateHighlight.bind( this ); - equationProperty.link( ( newEquation, oldEquation ) => { - if ( oldEquation ) { oldEquation.balancedProperty.unlink( balancedObserver ); } - newEquation.balancedProperty.link( balancedObserver ); - } ); - } - - // No dispose needed, instances of this type persist for lifetime of the sim. + // Wire observer to current equation. + const balancedObserver = this.updateHighlight.bind( this ); + equationProperty.link( ( newEquation, oldEquation ) => { + if ( oldEquation ) { oldEquation.balancedProperty.unlink( balancedObserver ); } + newEquation.balancedProperty.link( balancedObserver ); + } ); + } - // @private Highlights the arrow if the equation is balanced. - updateHighlight() { - this.fill = ( this.equationProperty.get().balancedProperty.get() && this._highlightEnabled ) - ? BCEConstants.BALANCED_HIGHLIGHT_COLOR : BCEConstants.UNBALANCED_COLOR; - } + // No dispose needed, instances of this type persist for lifetime of the sim. - // @public - set highlightEnabled( value ) { - this._highlightEnabled = value; - this.updateHighlight(); - } + // @private Highlights the arrow if the equation is balanced. + updateHighlight() { + this.fill = ( this.equationProperty.get().balancedProperty.get() && this._highlightEnabled ) + ? BCEConstants.BALANCED_HIGHLIGHT_COLOR : BCEConstants.UNBALANCED_COLOR; + } - // @public - get highlightEnabled() { return this._highlightEnabled; } + // @public + set highlightEnabled( value ) { + this._highlightEnabled = value; + this.updateHighlight(); } - return balancingChemicalEquations.register( 'RightArrowNode', RightArrowNode ); -} ); + // @public + get highlightEnabled() { return this._highlightEnabled; } +} + +balancingChemicalEquations.register( 'RightArrowNode', RightArrowNode ); +export default RightArrowNode; \ No newline at end of file diff --git a/js/common/view/TermNode.js b/js/common/view/TermNode.js index 70e772c5..f4301d6f 100644 --- a/js/common/view/TermNode.js +++ b/js/common/view/TermNode.js @@ -7,73 +7,70 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const NumberPicker = require( 'SCENERY_PHET/NumberPicker' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Property = require( 'AXON/Property' ); - const RichText = require( 'SCENERY/nodes/RichText' ); +import Property from '../../../../axon/js/Property.js'; +import merge from '../../../../phet-core/js/merge.js'; +import NumberPicker from '../../../../scenery-phet/js/NumberPicker.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import RichText from '../../../../scenery/js/nodes/RichText.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; - class TermNode extends Node { +class TermNode extends Node { - /** - * @param {DOT.Range} coefficientRange - * @param {EquationTerm} term - * @param {Object} [options] - */ - constructor( coefficientRange, term, options ) { + /** + * @param {DOT.Range} coefficientRange + * @param {EquationTerm} term + * @param {Object} [options] + */ + constructor( coefficientRange, term, options ) { - options = merge( { - fontSize: 32, - xSpacing: 4 - }, options ); + options = merge( { + fontSize: 32, + xSpacing: 4 + }, options ); - // coefficient picker - const coefficientNode = new NumberPicker( term.userCoefficientProperty, new Property( coefficientRange ), { - color: 'rgb(50,50,50)', - pressedColor: 'black', - xMargin: 8, - yMargin: 0, - touchAreaXDilation: 30, - font: new PhetFont( options.fontSize ), - timerDelay: 400, // ms until the picker starts to fire continuously - timerInterval: 200 // ms between value change while firing continuously - } ); + // coefficient picker + const coefficientNode = new NumberPicker( term.userCoefficientProperty, new Property( coefficientRange ), { + color: 'rgb(50,50,50)', + pressedColor: 'black', + xMargin: 8, + yMargin: 0, + touchAreaXDilation: 30, + font: new PhetFont( options.fontSize ), + timerDelay: 400, // ms until the picker starts to fire continuously + timerInterval: 200 // ms between value change while firing continuously + } ); - // symbol, non-subscript part of the symbol is vertically centered on the picker - const richTextOptions = { font: new PhetFont( options.fontSize ) }; - const symbolNode = new RichText( term.molecule.symbol, richTextOptions ); - symbolNode.left = coefficientNode.right + options.xSpacing; - symbolNode.centerY = coefficientNode.centerY + ( symbolNode.height - new RichText( 'H', richTextOptions ).height ) / 2; + // symbol, non-subscript part of the symbol is vertically centered on the picker + const richTextOptions = { font: new PhetFont( options.fontSize ) }; + const symbolNode = new RichText( term.molecule.symbol, richTextOptions ); + symbolNode.left = coefficientNode.right + options.xSpacing; + symbolNode.centerY = coefficientNode.centerY + ( symbolNode.height - new RichText( 'H', richTextOptions ).height ) / 2; - options.children = [ coefficientNode, symbolNode ]; - super( options ); + options.children = [ coefficientNode, symbolNode ]; + super( options ); - // @private - this.coefficientNode = coefficientNode; - } + // @private + this.coefficientNode = coefficientNode; + } - // @public - dispose() { - this.coefficientNode.dispose(); - super.dispose(); - } + // @public + dispose() { + this.coefficientNode.dispose(); + super.dispose(); + } - /** - * When a term is disabled, it is not pickable and the arrows on its picker are hidden. - * @param enabled - * @public - */ - setEnabled( enabled ) { - this.pickable = enabled; - this.coefficientNode.setArrowsVisible( enabled ); - } + /** + * When a term is disabled, it is not pickable and the arrows on its picker are hidden. + * @param enabled + * @public + */ + setEnabled( enabled ) { + this.pickable = enabled; + this.coefficientNode.setArrowsVisible( enabled ); } +} - return balancingChemicalEquations.register( 'TermNode', TermNode ); -} ); +balancingChemicalEquations.register( 'TermNode', TermNode ); +export default TermNode; \ No newline at end of file diff --git a/js/game/GameScreen.js b/js/game/GameScreen.js index 1370c47b..be83f533 100644 --- a/js/game/GameScreen.js +++ b/js/game/GameScreen.js @@ -5,77 +5,74 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const FaceNode = require( 'SCENERY_PHET/FaceNode' ); - const GameModel = require( 'BALANCING_CHEMICAL_EQUATIONS/game/model/GameModel' ); - const GameScreenView = require( 'BALANCING_CHEMICAL_EQUATIONS/game/view/GameScreenView' ); - const Node = require( 'SCENERY/nodes/Node' ); - const Path = require( 'SCENERY/nodes/Path' ); - const Property = require( 'AXON/Property' ); - const Rectangle = require( 'SCENERY/nodes/Rectangle' ); - const Screen = require( 'JOIST/Screen' ); - const Shape = require( 'KITE/Shape' ); +import Property from '../../../axon/js/Property.js'; +import Screen from '../../../joist/js/Screen.js'; +import Shape from '../../../kite/js/Shape.js'; +import FaceNode from '../../../scenery-phet/js/FaceNode.js'; +import Node from '../../../scenery/js/nodes/Node.js'; +import Path from '../../../scenery/js/nodes/Path.js'; +import Rectangle from '../../../scenery/js/nodes/Rectangle.js'; +import balancingChemicalEquationsStrings from '../balancing-chemical-equations-strings.js'; +import balancingChemicalEquations from '../balancingChemicalEquations.js'; +import BCEConstants from '../common/BCEConstants.js'; +import GameModel from './model/GameModel.js'; +import GameScreenView from './view/GameScreenView.js'; - // strings - const screenGameString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/screen.game' ); +const screenGameString = balancingChemicalEquationsStrings.screen.game; - class GameScreen extends Screen { +class GameScreen extends Screen { - constructor() { + constructor() { - const options = { - name: screenGameString, - backgroundColorProperty: new Property( BCEConstants.GAME_CANVAS_BACKGROUND ), - homeScreenIcon: createScreenIcon() - }; + const options = { + name: screenGameString, + backgroundColorProperty: new Property( BCEConstants.GAME_CANVAS_BACKGROUND ), + homeScreenIcon: createScreenIcon() + }; - super( - () => new GameModel(), - model => new GameScreenView( model ), - options - ); - } + super( + () => new GameModel(), + model => new GameScreenView( model ), + options + ); } +} - // creates the icon for this screen: a smiley face to the right of up/down arrows - function createScreenIcon() { +// creates the icon for this screen: a smiley face to the right of up/down arrows +function createScreenIcon() { - // constants - const faceDiameter = 200; - const arrowXSpacing = 25; - const arrowYSpacing = 10; + // constants + const faceDiameter = 200; + const arrowXSpacing = 25; + const arrowYSpacing = 10; - // background rectangle - const width = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE.width; - const height = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE.height; - const background = new Rectangle( 0, 0, width, height, { fill: 'white' } ); + // background rectangle + const width = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE.width; + const height = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE.height; + const background = new Rectangle( 0, 0, width, height, { fill: 'white' } ); - // face - const faceNode = new FaceNode( faceDiameter, { headStroke: 'black', headLineWidth: 4 } ); + // face + const faceNode = new FaceNode( faceDiameter, { headStroke: 'black', headLineWidth: 4 } ); - // up/down arrows - const arrowOptions = { fill: 'black' }; - const arrowSize = 0.4 * ( faceNode.height - arrowYSpacing ); - const upArrowNode = new Path( new Shape().moveTo( 0, 0 ).lineTo( arrowSize / 2, arrowSize ).lineTo( -arrowSize / 2, arrowSize ).close(), arrowOptions ); - const downArrowNode = new Path( new Shape().moveTo( 0, 0 ).lineTo( arrowSize / 2, -arrowSize ).lineTo( -arrowSize / 2, -arrowSize ).close(), arrowOptions ); + // up/down arrows + const arrowOptions = { fill: 'black' }; + const arrowSize = 0.4 * ( faceNode.height - arrowYSpacing ); + const upArrowNode = new Path( new Shape().moveTo( 0, 0 ).lineTo( arrowSize / 2, arrowSize ).lineTo( -arrowSize / 2, arrowSize ).close(), arrowOptions ); + const downArrowNode = new Path( new Shape().moveTo( 0, 0 ).lineTo( arrowSize / 2, -arrowSize ).lineTo( -arrowSize / 2, -arrowSize ).close(), arrowOptions ); - // layout, arrows to left of face - upArrowNode.right = faceNode.left - arrowXSpacing; - upArrowNode.bottom = faceNode.centerY - arrowYSpacing; - downArrowNode.right = faceNode.left - arrowXSpacing; - downArrowNode.top = faceNode.centerY + arrowYSpacing; + // layout, arrows to left of face + upArrowNode.right = faceNode.left - arrowXSpacing; + upArrowNode.bottom = faceNode.centerY - arrowYSpacing; + downArrowNode.right = faceNode.left - arrowXSpacing; + downArrowNode.top = faceNode.centerY + arrowYSpacing; - // scale to fit, center in background - const contentNode = new Node( { children: [ faceNode, upArrowNode, downArrowNode ] } ); - contentNode.setScaleMagnitude( Math.min( 0.82 * background.width / contentNode.width, 0.82 * background.height / contentNode.height ) ); - contentNode.center = background.center; - return new Node( { children: [ background, contentNode ] } ); - } + // scale to fit, center in background + const contentNode = new Node( { children: [ faceNode, upArrowNode, downArrowNode ] } ); + contentNode.setScaleMagnitude( Math.min( 0.82 * background.width / contentNode.width, 0.82 * background.height / contentNode.height ) ); + contentNode.center = background.center; + return new Node( { children: [ background, contentNode ] } ); +} - return balancingChemicalEquations.register( 'GameScreen', GameScreen ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'GameScreen', GameScreen ); +export default GameScreen; \ No newline at end of file diff --git a/js/game/model/GameFactory.js b/js/game/model/GameFactory.js index e66c1f80..775472c2 100644 --- a/js/game/model/GameFactory.js +++ b/js/game/model/GameFactory.js @@ -12,207 +12,204 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEQueryParameters = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEQueryParameters' ); - const DecompositionEquation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/DecompositionEquation' ); - const DisplacementEquation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/DisplacementEquation' ); - const RandomStrategy = require( 'BALANCING_CHEMICAL_EQUATIONS/game/model/RandomStrategy' ); - const SynthesisEquation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/SynthesisEquation' ); +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEQueryParameters from '../../common/BCEQueryParameters.js'; +import DecompositionEquation from '../../common/model/DecompositionEquation.js'; +import DisplacementEquation from '../../common/model/DisplacementEquation.js'; +import SynthesisEquation from '../../common/model/SynthesisEquation.js'; +import RandomStrategy from './RandomStrategy.js'; - // constants - const EQUATIONS_PER_GAME = 5; +// constants +const EQUATIONS_PER_GAME = 5; - // Level 1 equation pool - const LEVEL1_POOL = [ - // this is the largest molecule, put it first to simplify layout testing - DecompositionEquation.create_PCl5_PCl3_Cl2, - // this equation requires maxX adjustment in EquationNode, put it here to simplify layout testing - DecompositionEquation.create_CH3OH_CO_2H2, - SynthesisEquation.create_CH2O_H2_CH3OH, - SynthesisEquation.create_2H2_O2_2H2O, - SynthesisEquation.create_H2_F2_2HF, - DecompositionEquation.create_2HCl_H2_Cl2, - SynthesisEquation.create_CH2O_H2_CH3OH, - DecompositionEquation.create_C2H6_C2H4_H2, - SynthesisEquation.create_C2H2_2H2_C2H6, - SynthesisEquation.create_C_O2_CO2, - SynthesisEquation.create_2C_O2_2CO, - DecompositionEquation.create_2CO2_2CO_O2, - DecompositionEquation.create_2CO_C_CO2, - SynthesisEquation.create_C_2S_CS2, - DecompositionEquation.create_2NH3_N2_3H2, - DecompositionEquation.create_2NO_N2_O2, - DecompositionEquation.create_2NO2_2NO_O2, - SynthesisEquation.create_2N2_O2_2N2O, - SynthesisEquation.create_P4_6H2_4PH3, - SynthesisEquation.create_P4_6F2_4PF3, - DecompositionEquation.create_4PCl3_P4_6Cl2, - DecompositionEquation.create_2SO3_2SO2_O2 - ]; +// Level 1 equation pool +const LEVEL1_POOL = [ + // this is the largest molecule, put it first to simplify layout testing + DecompositionEquation.create_PCl5_PCl3_Cl2, + // this equation requires maxX adjustment in EquationNode, put it here to simplify layout testing + DecompositionEquation.create_CH3OH_CO_2H2, + SynthesisEquation.create_CH2O_H2_CH3OH, + SynthesisEquation.create_2H2_O2_2H2O, + SynthesisEquation.create_H2_F2_2HF, + DecompositionEquation.create_2HCl_H2_Cl2, + SynthesisEquation.create_CH2O_H2_CH3OH, + DecompositionEquation.create_C2H6_C2H4_H2, + SynthesisEquation.create_C2H2_2H2_C2H6, + SynthesisEquation.create_C_O2_CO2, + SynthesisEquation.create_2C_O2_2CO, + DecompositionEquation.create_2CO2_2CO_O2, + DecompositionEquation.create_2CO_C_CO2, + SynthesisEquation.create_C_2S_CS2, + DecompositionEquation.create_2NH3_N2_3H2, + DecompositionEquation.create_2NO_N2_O2, + DecompositionEquation.create_2NO2_2NO_O2, + SynthesisEquation.create_2N2_O2_2N2O, + SynthesisEquation.create_P4_6H2_4PH3, + SynthesisEquation.create_P4_6F2_4PF3, + DecompositionEquation.create_4PCl3_P4_6Cl2, + DecompositionEquation.create_2SO3_2SO2_O2 +]; - // Level 2 equation pool - const LEVEL2_POOL = [ - DisplacementEquation.create_2C_2H2O_CH4_CO2, - DisplacementEquation.create_CH4_H2O_3H2_CO, - DisplacementEquation.create_CH4_2O2_CO2_2H2O, - DisplacementEquation.create_C2H4_3O2_2CO2_2H2O, - DisplacementEquation.create_C2H6_Cl2_C2H5Cl_HCl, - DisplacementEquation.create_CH4_4S_CS2_2H2S, - DisplacementEquation.create_CS2_3O2_CO2_2SO2, - DisplacementEquation.create_SO2_2H2_S_2H2O, - DisplacementEquation.create_SO2_3H2_H2S_2H2O, - DisplacementEquation.create_2F2_H2O_OF2_2HF, - DisplacementEquation.create_OF2_H2O_O2_2HF - ]; +// Level 2 equation pool +const LEVEL2_POOL = [ + DisplacementEquation.create_2C_2H2O_CH4_CO2, + DisplacementEquation.create_CH4_H2O_3H2_CO, + DisplacementEquation.create_CH4_2O2_CO2_2H2O, + DisplacementEquation.create_C2H4_3O2_2CO2_2H2O, + DisplacementEquation.create_C2H6_Cl2_C2H5Cl_HCl, + DisplacementEquation.create_CH4_4S_CS2_2H2S, + DisplacementEquation.create_CS2_3O2_CO2_2SO2, + DisplacementEquation.create_SO2_2H2_S_2H2O, + DisplacementEquation.create_SO2_3H2_H2S_2H2O, + DisplacementEquation.create_2F2_H2O_OF2_2HF, + DisplacementEquation.create_OF2_H2O_O2_2HF +]; - // Level 3 equation pool - const LEVEL3_POOL = [ - // this is the longest equation, requires minX adjustment in EquationNode, put it first to simplify layout testing - DisplacementEquation.create_C2H5OH_3O2_2CO2_3H2O, - // this is the reverse of the previous equation - DisplacementEquation.create_2CO2_3H2O_C2H5OH_3O2, - DisplacementEquation.create_2C2H6_7O2_4CO2_6H2O, - DisplacementEquation.create_4CO2_6H2O_2C2H6_7O2, - DisplacementEquation.create_2C2H2_5O2_4CO2_2H2O, - DisplacementEquation.create_4CO2_2H2O_2C2H2_5O2, - DisplacementEquation.create_4NH3_3O2_2N2_6H2O, - DisplacementEquation.create_2N2_6H2O_4NH3_3O2, - DisplacementEquation.create_4NH3_5O2_4NO_6H2O, - DisplacementEquation.create_4NO_6H2O_4NH3_5O2, +// Level 3 equation pool +const LEVEL3_POOL = [ + // this is the longest equation, requires minX adjustment in EquationNode, put it first to simplify layout testing + DisplacementEquation.create_C2H5OH_3O2_2CO2_3H2O, + // this is the reverse of the previous equation + DisplacementEquation.create_2CO2_3H2O_C2H5OH_3O2, + DisplacementEquation.create_2C2H6_7O2_4CO2_6H2O, + DisplacementEquation.create_4CO2_6H2O_2C2H6_7O2, + DisplacementEquation.create_2C2H2_5O2_4CO2_2H2O, + DisplacementEquation.create_4CO2_2H2O_2C2H2_5O2, + DisplacementEquation.create_4NH3_3O2_2N2_6H2O, + DisplacementEquation.create_2N2_6H2O_4NH3_3O2, + DisplacementEquation.create_4NH3_5O2_4NO_6H2O, + DisplacementEquation.create_4NO_6H2O_4NH3_5O2, + DisplacementEquation.create_4NH3_7O2_4NO2_6H2O, + DisplacementEquation.create_4NO2_6H2O_4NH3_7O2, + DisplacementEquation.create_4NH3_6NO_5N2_6H2O, + DisplacementEquation.create_5N2_6H2O_4NH3_6NO +]; + +// all pools, indexed by level +const POOLS = [ LEVEL1_POOL, LEVEL2_POOL, LEVEL3_POOL ]; + +/* + * Level 3 exclusions map + * This mess deserves some explanation... For level 3, the design team wanted a complicated + * strategy for selecting equations, where selection of an equation causes other equations to be + * ruled out as possible choices. For example, if we choose an equation that contains 4NH3 as + * a reactant, we don't want to choose any other equations with 4NH3 as a reactant, and we don't + * want to choose the reverse equation. Since this "exclusion" strategy was a moving target and + * the rules kept changing, I implemented this general solution, whereby a list of exclusions + * can be specified for each equation. + */ +const LEVEL3_EXCLUSIONS = { + create_2C2H6_7O2_4CO2_6H2O: [ + DisplacementEquation.create_4CO2_6H2O_2C2H6_7O2, /* reverse equation */ + DisplacementEquation.create_2C2H2_5O2_4CO2_2H2O + ], + create_4CO2_6H2O_2C2H6_7O2: [ + DisplacementEquation.create_2C2H6_7O2_4CO2_6H2O, /* reverse equation */ + DisplacementEquation.create_4CO2_2H2O_2C2H2_5O2 + ], + create_2C2H2_5O2_4CO2_2H2O: [ + DisplacementEquation.create_4CO2_2H2O_2C2H2_5O2, /* reverse equation */ + DisplacementEquation.create_2C2H6_7O2_4CO2_6H2O + ], + create_4CO2_2H2O_2C2H2_5O2: [ + DisplacementEquation.create_2C2H2_5O2_4CO2_2H2O, /* reverse equation */ + DisplacementEquation.create_4CO2_6H2O_2C2H6_7O2 + ], + create_C2H5OH_3O2_2CO2_3H2O: [ + DisplacementEquation.create_2CO2_3H2O_C2H5OH_3O2 /* reverse equation */ + ], + create_2CO2_3H2O_C2H5OH_3O2: [ + DisplacementEquation.create_C2H5OH_3O2_2CO2_3H2O /* reverse equation */ + ], + create_4NH3_3O2_2N2_6H2O: [ + DisplacementEquation.create_2N2_6H2O_4NH3_3O2, /* reverse equation */ + DisplacementEquation.create_4NH3_5O2_4NO_6H2O, /* other equations with reactant 4NH3 */ DisplacementEquation.create_4NH3_7O2_4NO2_6H2O, + DisplacementEquation.create_4NH3_6NO_5N2_6H2O + ], + create_4NH3_5O2_4NO_6H2O: [ + DisplacementEquation.create_4NO_6H2O_4NH3_5O2, /* reverse equation */ + DisplacementEquation.create_4NH3_3O2_2N2_6H2O, /* other equations with reactant 4NH3 */ + DisplacementEquation.create_4NH3_7O2_4NO2_6H2O, + DisplacementEquation.create_4NH3_6NO_5N2_6H2O + ], + create_4NH3_7O2_4NO2_6H2O: [ + DisplacementEquation.create_4NO2_6H2O_4NH3_7O2, /* reverse equation */ + DisplacementEquation.create_4NH3_3O2_2N2_6H2O, /* other equations with reactant 4NH3 */ + DisplacementEquation.create_4NH3_5O2_4NO_6H2O, + DisplacementEquation.create_4NH3_6NO_5N2_6H2O + ], + create_4NH3_6NO_5N2_6H2O: [ + DisplacementEquation.create_5N2_6H2O_4NH3_6NO, /* reverse equation */ + DisplacementEquation.create_4NH3_3O2_2N2_6H2O, /* other equations with reactant 4NH3 */ + DisplacementEquation.create_4NH3_5O2_4NO_6H2O, + DisplacementEquation.create_4NH3_7O2_4NO2_6H2O + ], + create_2N2_6H2O_4NH3_3O2: [ + DisplacementEquation.create_4NH3_3O2_2N2_6H2O, /* reverse equation */ + DisplacementEquation.create_4NO_6H2O_4NH3_5O2, /* other equations with product 4NH3 */ DisplacementEquation.create_4NO2_6H2O_4NH3_7O2, - DisplacementEquation.create_4NH3_6NO_5N2_6H2O, DisplacementEquation.create_5N2_6H2O_4NH3_6NO - ]; - - // all pools, indexed by level - const POOLS = [ LEVEL1_POOL, LEVEL2_POOL, LEVEL3_POOL ]; - - /* - * Level 3 exclusions map - * This mess deserves some explanation... For level 3, the design team wanted a complicated - * strategy for selecting equations, where selection of an equation causes other equations to be - * ruled out as possible choices. For example, if we choose an equation that contains 4NH3 as - * a reactant, we don't want to choose any other equations with 4NH3 as a reactant, and we don't - * want to choose the reverse equation. Since this "exclusion" strategy was a moving target and - * the rules kept changing, I implemented this general solution, whereby a list of exclusions - * can be specified for each equation. - */ - const LEVEL3_EXCLUSIONS = { - create_2C2H6_7O2_4CO2_6H2O: [ - DisplacementEquation.create_4CO2_6H2O_2C2H6_7O2, /* reverse equation */ - DisplacementEquation.create_2C2H2_5O2_4CO2_2H2O - ], - create_4CO2_6H2O_2C2H6_7O2: [ - DisplacementEquation.create_2C2H6_7O2_4CO2_6H2O, /* reverse equation */ - DisplacementEquation.create_4CO2_2H2O_2C2H2_5O2 - ], - create_2C2H2_5O2_4CO2_2H2O: [ - DisplacementEquation.create_4CO2_2H2O_2C2H2_5O2, /* reverse equation */ - DisplacementEquation.create_2C2H6_7O2_4CO2_6H2O - ], - create_4CO2_2H2O_2C2H2_5O2: [ - DisplacementEquation.create_2C2H2_5O2_4CO2_2H2O, /* reverse equation */ - DisplacementEquation.create_4CO2_6H2O_2C2H6_7O2 - ], - create_C2H5OH_3O2_2CO2_3H2O: [ - DisplacementEquation.create_2CO2_3H2O_C2H5OH_3O2 /* reverse equation */ - ], - create_2CO2_3H2O_C2H5OH_3O2: [ - DisplacementEquation.create_C2H5OH_3O2_2CO2_3H2O /* reverse equation */ - ], - create_4NH3_3O2_2N2_6H2O: [ - DisplacementEquation.create_2N2_6H2O_4NH3_3O2, /* reverse equation */ - DisplacementEquation.create_4NH3_5O2_4NO_6H2O, /* other equations with reactant 4NH3 */ - DisplacementEquation.create_4NH3_7O2_4NO2_6H2O, - DisplacementEquation.create_4NH3_6NO_5N2_6H2O - ], - create_4NH3_5O2_4NO_6H2O: [ - DisplacementEquation.create_4NO_6H2O_4NH3_5O2, /* reverse equation */ - DisplacementEquation.create_4NH3_3O2_2N2_6H2O, /* other equations with reactant 4NH3 */ - DisplacementEquation.create_4NH3_7O2_4NO2_6H2O, - DisplacementEquation.create_4NH3_6NO_5N2_6H2O - ], - create_4NH3_7O2_4NO2_6H2O: [ - DisplacementEquation.create_4NO2_6H2O_4NH3_7O2, /* reverse equation */ - DisplacementEquation.create_4NH3_3O2_2N2_6H2O, /* other equations with reactant 4NH3 */ - DisplacementEquation.create_4NH3_5O2_4NO_6H2O, - DisplacementEquation.create_4NH3_6NO_5N2_6H2O - ], - create_4NH3_6NO_5N2_6H2O: [ - DisplacementEquation.create_5N2_6H2O_4NH3_6NO, /* reverse equation */ - DisplacementEquation.create_4NH3_3O2_2N2_6H2O, /* other equations with reactant 4NH3 */ - DisplacementEquation.create_4NH3_5O2_4NO_6H2O, - DisplacementEquation.create_4NH3_7O2_4NO2_6H2O - ], - create_2N2_6H2O_4NH3_3O2: [ - DisplacementEquation.create_4NH3_3O2_2N2_6H2O, /* reverse equation */ - DisplacementEquation.create_4NO_6H2O_4NH3_5O2, /* other equations with product 4NH3 */ - DisplacementEquation.create_4NO2_6H2O_4NH3_7O2, - DisplacementEquation.create_5N2_6H2O_4NH3_6NO - ], - create_4NO_6H2O_4NH3_5O2: [ - DisplacementEquation.create_4NH3_5O2_4NO_6H2O, /* reverse equation */ - DisplacementEquation.create_2N2_6H2O_4NH3_3O2, /* other equations with product 4NH3 */ - DisplacementEquation.create_4NO2_6H2O_4NH3_7O2, - DisplacementEquation.create_5N2_6H2O_4NH3_6NO - ], - create_4NO2_6H2O_4NH3_7O2: [ - DisplacementEquation.create_4NH3_7O2_4NO2_6H2O, /* reverse equation */ - DisplacementEquation.create_2N2_6H2O_4NH3_3O2, /* other equations with product 4NH3 */ - DisplacementEquation.create_4NO_6H2O_4NH3_5O2, - DisplacementEquation.create_5N2_6H2O_4NH3_6NO - ], - create_5N2_6H2O_4NH3_6NO: [ - DisplacementEquation.create_4NH3_6NO_5N2_6H2O, /* reverse equation */ - DisplacementEquation.create_2N2_6H2O_4NH3_3O2, /* other equations with product 4NH3 */ - DisplacementEquation.create_4NO_6H2O_4NH3_5O2, - DisplacementEquation.create_4NO2_6H2O_4NH3_7O2 - ] - }; + ], + create_4NO_6H2O_4NH3_5O2: [ + DisplacementEquation.create_4NH3_5O2_4NO_6H2O, /* reverse equation */ + DisplacementEquation.create_2N2_6H2O_4NH3_3O2, /* other equations with product 4NH3 */ + DisplacementEquation.create_4NO2_6H2O_4NH3_7O2, + DisplacementEquation.create_5N2_6H2O_4NH3_6NO + ], + create_4NO2_6H2O_4NH3_7O2: [ + DisplacementEquation.create_4NH3_7O2_4NO2_6H2O, /* reverse equation */ + DisplacementEquation.create_2N2_6H2O_4NH3_3O2, /* other equations with product 4NH3 */ + DisplacementEquation.create_4NO_6H2O_4NH3_5O2, + DisplacementEquation.create_5N2_6H2O_4NH3_6NO + ], + create_5N2_6H2O_4NH3_6NO: [ + DisplacementEquation.create_4NH3_6NO_5N2_6H2O, /* reverse equation */ + DisplacementEquation.create_2N2_6H2O_4NH3_3O2, /* other equations with product 4NH3 */ + DisplacementEquation.create_4NO_6H2O_4NH3_5O2, + DisplacementEquation.create_4NO2_6H2O_4NH3_7O2 + ] +}; - // strategies for selecting equations, indexed by game level - const STRATEGIES = [ - new RandomStrategy( LEVEL1_POOL, false ), - new RandomStrategy( LEVEL2_POOL, true ), - new RandomStrategy( LEVEL3_POOL, true, { exclusions: LEVEL3_EXCLUSIONS } ) - ]; +// strategies for selecting equations, indexed by game level +const STRATEGIES = [ + new RandomStrategy( LEVEL1_POOL, false ), + new RandomStrategy( LEVEL2_POOL, true ), + new RandomStrategy( LEVEL3_POOL, true, { exclusions: LEVEL3_EXCLUSIONS } ) +]; - const GameFactory = { +const GameFactory = { - /** - * Gets the number of equations for a level. - * If we're playing all equations for testing purposes, return the entire pool length. - * @param {number} level - * @returns {Window.length|*} - */ - getNumberOfEquations: function( level ) { - return BCEQueryParameters.playAll ? POOLS[ level ].length : EQUATIONS_PER_GAME; - }, + /** + * Gets the number of equations for a level. + * If we're playing all equations for testing purposes, return the entire pool length. + * @param {number} level + * @returns {Window.length|*} + */ + getNumberOfEquations: function( level ) { + return BCEQueryParameters.playAll ? POOLS[ level ].length : EQUATIONS_PER_GAME; + }, - /** - * Creates a set of equations to be used in the game. - * If 'playAll' query parameter is defined, return all equations for the level. - * @param {number} level - * @returns {Equation} - */ - createEquations: function( level ) { + /** + * Creates a set of equations to be used in the game. + * If 'playAll' query parameter is defined, return all equations for the level. + * @param {number} level + * @returns {Equation} + */ + createEquations: function( level ) { - // Get an array of Equation factory functions. - const factoryFunctions = BCEQueryParameters.playAll ? + // Get an array of Equation factory functions. + const factoryFunctions = BCEQueryParameters.playAll ? _.clone( POOLS[ level ] ) : STRATEGIES[ level ].getEquationFactoryFunctions( EQUATIONS_PER_GAME ); - // Instantiate one instance of each Equation type. - const equations = []; - factoryFunctions.forEach( factoryFunction => equations.push( factoryFunction() ) ); - return equations; - } - }; + // Instantiate one instance of each Equation type. + const equations = []; + factoryFunctions.forEach( factoryFunction => equations.push( factoryFunction() ) ); + return equations; + } +}; - return balancingChemicalEquations.register( 'GameFactory', GameFactory ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'GameFactory', GameFactory ); +export default GameFactory; \ No newline at end of file diff --git a/js/game/model/GameModel.js b/js/game/model/GameModel.js index 16f8a078..dde689ef 100644 --- a/js/game/model/GameModel.js +++ b/js/game/model/GameModel.js @@ -5,265 +5,262 @@ * * @author Vasily Shakhov (MLearner) */ -define( require => { - 'use strict'; - - // modules - const BalancedRepresentation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/BalancedRepresentation' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const GameFactory = require( 'BALANCING_CHEMICAL_EQUATIONS/game/model/GameFactory' ); - const GameTimer = require( 'VEGAS/GameTimer' ); - const NumberProperty = require( 'AXON/NumberProperty' ); - const Property = require( 'AXON/Property' ); - const Range = require( 'DOT/Range' ); - const StringProperty = require( 'AXON/StringProperty' ); - const SynthesisEquation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/SynthesisEquation' ); - - // constants - /* - * Strategies for selecting the "balanced representation" that is displayed by the "Not Balanced" popup. - * This is a map from level to strategy. - */ - const BALANCED_REPRESENTATION_STRATEGIES = [ - - // level 1 - () => BalancedRepresentation.BALANCE_SCALES, - - //level 2 - () => phet.joist.random.nextDouble() < 0.5 ? BalancedRepresentation.BALANCE_SCALES : BalancedRepresentation.BAR_CHARTS, - - // level 3 - () => BalancedRepresentation.BAR_CHARTS - ]; - const POINTS_FIRST_ATTEMPT = 2; // points to award for correct guess on 1st attempt - const POINTS_SECOND_ATTEMPT = 1; // points to award for correct guess on 2nd attempt - - class GameModel { - - constructor() { - - /* - * @public - * The set of game states. - * For lack of better names, the state names correspond to the main action that - * the user can take in that state. For example. the CHECK state is where the user - * can enter coefficients and press the "Check" button to check their answer. - */ - this.states = { - LEVEL_SELECTION: 'LevelSelection', //level selection screen - CHECK: 'Check', - TRY_AGAIN: 'TryAgain', - SHOW_ANSWER: 'ShowAnswer', - NEXT: 'Next', - LEVEL_COMPLETED: 'LevelCompleted' //reward node - }; - - // @public (read-only) constants - this.COEFFICENTS_RANGE = new Range( 0, 7 ); // Range of possible equation coefficients - this.LEVELS_RANGE = new Range( 0, 2 ); // Levels 1-2-3, counting from 0 - - // @public - this.stateProperty = new StringProperty( this.states.LEVEL_SELECTION ); - // level of the current game - this.levelProperty = new NumberProperty( 0, { numberType: 'Integer' } ); - // how many points the user has earned for the current game - this.pointsProperty = new NumberProperty( 0, { numberType: 'Integer' } ); - // number of challenges in the current game - this.numberOfEquationsProperty = new NumberProperty( 0, { numberType: 'Integer' } ); - // any non-null {Equation} will do here - this.currentEquationProperty = new Property( SynthesisEquation.create_N2_3H2_2NH3() ); - // index of the current challenge that the user is working on - this.currentEquationIndexProperty = new NumberProperty( 0, { numberType: 'Integer' } ); - - this.equations = []; // @public array of Equation - this.timer = new GameTimer(); // @public - this.attempts = 0; // @private how many attempts the user has made at solving the current challenge - this.currentPoints = 0; // @public how many points were earned for the current challenge - this.balancedRepresentation = null; // @public which representation to use in the "Not Balanced" popup - this.isNewBestTime = false; // @public is the time for this game a new best time? - - this.bestTimeProperties = [];// @public {Property.[]} best times in ms, indexed by level - this.bestScoreProperties = []; // @public {Property.[]} best scores, indexed by level - for ( let i = this.LEVELS_RANGE.min; i <= this.LEVELS_RANGE.max; i++ ) { - this.bestTimeProperties[ i ] = new NumberProperty( 0, { numberType: 'Integer' } ); - this.bestScoreProperties[ i ] = new NumberProperty( 0, { numberType: 'Integer' } ); - } - } - // @public - reset() { - this.stateProperty.reset(); - this.levelProperty.reset(); - this.pointsProperty.reset(); - this.numberOfEquationsProperty.reset(); - this.currentEquationProperty.reset(); - this.currentEquationIndexProperty.reset(); - this.bestTimeProperties.forEach( bestTimeProperty => bestTimeProperty.reset() ); - this.bestScoreProperties.forEach( bestScoreProperty => bestScoreProperty.reset() ); - } +import NumberProperty from '../../../../axon/js/NumberProperty.js'; +import Property from '../../../../axon/js/Property.js'; +import StringProperty from '../../../../axon/js/StringProperty.js'; +import Range from '../../../../dot/js/Range.js'; +import GameTimer from '../../../../vegas/js/GameTimer.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BalancedRepresentation from '../../common/model/BalancedRepresentation.js'; +import SynthesisEquation from '../../common/model/SynthesisEquation.js'; +import GameFactory from './GameFactory.js'; + +// constants +/* + * Strategies for selecting the "balanced representation" that is displayed by the "Not Balanced" popup. + * This is a map from level to strategy. + */ +const BALANCED_REPRESENTATION_STRATEGIES = [ - /** - * Called when the user presses a level-selection button. - * @public - */ - startGame() { + // level 1 + () => BalancedRepresentation.BALANCE_SCALES, - const level = this.levelProperty.get(); + //level 2 + () => phet.joist.random.nextDouble() < 0.5 ? BalancedRepresentation.BALANCE_SCALES : BalancedRepresentation.BAR_CHARTS, - // create a set of challenges - this.equations = GameFactory.createEquations( level ); + // level 3 + () => BalancedRepresentation.BAR_CHARTS +]; +const POINTS_FIRST_ATTEMPT = 2; // points to award for correct guess on 1st attempt +const POINTS_SECOND_ATTEMPT = 1; // points to award for correct guess on 2nd attempt - // initialize simple fields - this.attempts = 0; - this.currentPoints = 0; - this.isNewBestTime = false; - this.balancedRepresentation = BALANCED_REPRESENTATION_STRATEGIES[ level ](); - this.timer.restart(); +class GameModel { - // initialize properties - this.currentEquationIndexProperty.set( 0 ); - this.currentEquationProperty.set( this.equations[ this.currentEquationIndexProperty.get() ] ); - this.numberOfEquationsProperty.set( this.equations.length ); - this.pointsProperty.set( 0 ); - this.stateProperty.set( this.states.CHECK ); - } + constructor() { - /** - * Called when the user presses the "Check" button. + /* * @public + * The set of game states. + * For lack of better names, the state names correspond to the main action that + * the user can take in that state. For example. the CHECK state is where the user + * can enter coefficients and press the "Check" button to check their answer. */ - check() { - this.attempts++; - - if ( this.currentEquationProperty.get().balancedAndSimplified ) { - // award points - if ( this.attempts === 1 ) { - this.currentPoints = POINTS_FIRST_ATTEMPT; - - } - else if ( this.attempts === 2 ) { - this.currentPoints = POINTS_SECOND_ATTEMPT; - } - else { - this.currentPoints = 0; - } - this.pointsProperty.set( this.pointsProperty.get() + this.currentPoints ); - this.stateProperty.set( this.states.NEXT ); - - if ( this.currentEquationIndexProperty.get() === this.equations.length - 1 ) { - this.endGame(); - } - } - else if ( this.attempts < 2 ) { - this.stateProperty.set( this.states.TRY_AGAIN ); - } - else { - if ( this.currentEquationIndexProperty.get() === this.equations.length - 1 ) { - this.endGame(); - } - this.stateProperty.set( this.states.SHOW_ANSWER ); - } + this.states = { + LEVEL_SELECTION: 'LevelSelection', //level selection screen + CHECK: 'Check', + TRY_AGAIN: 'TryAgain', + SHOW_ANSWER: 'ShowAnswer', + NEXT: 'Next', + LEVEL_COMPLETED: 'LevelCompleted' //reward node + }; + + // @public (read-only) constants + this.COEFFICENTS_RANGE = new Range( 0, 7 ); // Range of possible equation coefficients + this.LEVELS_RANGE = new Range( 0, 2 ); // Levels 1-2-3, counting from 0 + + // @public + this.stateProperty = new StringProperty( this.states.LEVEL_SELECTION ); + // level of the current game + this.levelProperty = new NumberProperty( 0, { numberType: 'Integer' } ); + // how many points the user has earned for the current game + this.pointsProperty = new NumberProperty( 0, { numberType: 'Integer' } ); + // number of challenges in the current game + this.numberOfEquationsProperty = new NumberProperty( 0, { numberType: 'Integer' } ); + // any non-null {Equation} will do here + this.currentEquationProperty = new Property( SynthesisEquation.create_N2_3H2_2NH3() ); + // index of the current challenge that the user is working on + this.currentEquationIndexProperty = new NumberProperty( 0, { numberType: 'Integer' } ); + + this.equations = []; // @public array of Equation + this.timer = new GameTimer(); // @public + this.attempts = 0; // @private how many attempts the user has made at solving the current challenge + this.currentPoints = 0; // @public how many points were earned for the current challenge + this.balancedRepresentation = null; // @public which representation to use in the "Not Balanced" popup + this.isNewBestTime = false; // @public is the time for this game a new best time? + + this.bestTimeProperties = [];// @public {Property.[]} best times in ms, indexed by level + this.bestScoreProperties = []; // @public {Property.[]} best scores, indexed by level + for ( let i = this.LEVELS_RANGE.min; i <= this.LEVELS_RANGE.max; i++ ) { + this.bestTimeProperties[ i ] = new NumberProperty( 0, { numberType: 'Integer' } ); + this.bestScoreProperties[ i ] = new NumberProperty( 0, { numberType: 'Integer' } ); } + } - /** - * When a game ends, stop the timer and (if perfect score) set the new best time. - * @private - */ - endGame() { + // @public + reset() { + this.stateProperty.reset(); + this.levelProperty.reset(); + this.pointsProperty.reset(); + this.numberOfEquationsProperty.reset(); + this.currentEquationProperty.reset(); + this.currentEquationIndexProperty.reset(); + this.bestTimeProperties.forEach( bestTimeProperty => bestTimeProperty.reset() ); + this.bestScoreProperties.forEach( bestScoreProperty => bestScoreProperty.reset() ); + } + + /** + * Called when the user presses a level-selection button. + * @public + */ + startGame() { + + const level = this.levelProperty.get(); + + // create a set of challenges + this.equations = GameFactory.createEquations( level ); + + // initialize simple fields + this.attempts = 0; + this.currentPoints = 0; + this.isNewBestTime = false; + this.balancedRepresentation = BALANCED_REPRESENTATION_STRATEGIES[ level ](); + this.timer.restart(); + + // initialize properties + this.currentEquationIndexProperty.set( 0 ); + this.currentEquationProperty.set( this.equations[ this.currentEquationIndexProperty.get() ] ); + this.numberOfEquationsProperty.set( this.equations.length ); + this.pointsProperty.set( 0 ); + this.stateProperty.set( this.states.CHECK ); + } - this.timer.stop(); + /** + * Called when the user presses the "Check" button. + * @public + */ + check() { + this.attempts++; - const level = this.levelProperty.get(); - const points = this.pointsProperty.get(); + if ( this.currentEquationProperty.get().balancedAndSimplified ) { + // award points + if ( this.attempts === 1 ) { + this.currentPoints = POINTS_FIRST_ATTEMPT; - //check for new best score - if ( points > this.bestScoreProperties[ level ].get() ) { - this.bestScoreProperties[ level ].set( points ); } + else if ( this.attempts === 2 ) { + this.currentPoints = POINTS_SECOND_ATTEMPT; + } + else { + this.currentPoints = 0; + } + this.pointsProperty.set( this.pointsProperty.get() + this.currentPoints ); + this.stateProperty.set( this.states.NEXT ); - // check for new best time - const previousBestTime = this.bestTimeProperties[ level ].get(); - if ( this.isPerfectScore() && ( previousBestTime === 0 || this.timer.elapsedTimeProperty.value < previousBestTime ) ) { - this.isNewBestTime = true; - this.bestTimeProperties[ level ].set( this.timer.elapsedTimeProperty.value ); + if ( this.currentEquationIndexProperty.get() === this.equations.length - 1 ) { + this.endGame(); } } - - /** - * Called when the user presses the "Try Again" button. - * @public - */ - tryAgain() { - this.stateProperty.set( this.states.CHECK ); + else if ( this.attempts < 2 ) { + this.stateProperty.set( this.states.TRY_AGAIN ); } - - /** - * Called when the user presses the "Show Answer" button. - * @public - */ - showAnswer() { - this.stateProperty.set( this.states.NEXT ); + else { + if ( this.currentEquationIndexProperty.get() === this.equations.length - 1 ) { + this.endGame(); + } + this.stateProperty.set( this.states.SHOW_ANSWER ); } + } - /** - * Gets the number of equations for a specified level. - * @param level - * @returns {number} - * @public - */ - getNumberOfEquations( level ) { - return GameFactory.getNumberOfEquations( level ); - } + /** + * When a game ends, stop the timer and (if perfect score) set the new best time. + * @private + */ + endGame() { - /** - * Gets the number of points in a perfect score for a specified level. - * A perfect score is obtained when the user balances every equation correctly on the first attempt. - * @param level - * @returns {number} - * @public - */ - getPerfectScore( level ) { - return this.getNumberOfEquations( level ) * POINTS_FIRST_ATTEMPT; - } + this.timer.stop(); - /** - * Is the current score a perfect score? - * This can be called at any time during the game, but can't possibly - * return true until the game has been completed. - * @returns {boolean} - * @public - */ - isPerfectScore() { - return this.pointsProperty.get() === this.getPerfectScore( this.levelProperty.get() ); + const level = this.levelProperty.get(); + const points = this.pointsProperty.get(); + + //check for new best score + if ( points > this.bestScoreProperties[ level ].get() ) { + this.bestScoreProperties[ level ].set( points ); } - /** - * Called when the user presses the "Start Over" button. - * @public - */ - newGame() { - this.stateProperty.set( this.states.LEVEL_SELECTION ); - this.timer.restart(); + // check for new best time + const previousBestTime = this.bestTimeProperties[ level ].get(); + if ( this.isPerfectScore() && ( previousBestTime === 0 || this.timer.elapsedTimeProperty.value < previousBestTime ) ) { + this.isNewBestTime = true; + this.bestTimeProperties[ level ].set( this.timer.elapsedTimeProperty.value ); } + } - /** - * Called when the user presses the "Next" button. - * @public - */ - next() { - if ( this.currentEquationIndexProperty.get() < this.equations.length - 1 ) { - this.attempts = 0; - this.currentPoints = 0; - this.balancedRepresentation = BALANCED_REPRESENTATION_STRATEGIES[ this.levelProperty.get() ](); - this.currentEquationIndexProperty.set( this.currentEquationIndexProperty.get() + 1 ); - this.currentEquationProperty.set( this.equations[ this.currentEquationIndexProperty.get() ] ); - this.stateProperty.set( this.states.CHECK ); - } - else { - this.stateProperty.set( this.states.LEVEL_COMPLETED ); - } + /** + * Called when the user presses the "Try Again" button. + * @public + */ + tryAgain() { + this.stateProperty.set( this.states.CHECK ); + } + + /** + * Called when the user presses the "Show Answer" button. + * @public + */ + showAnswer() { + this.stateProperty.set( this.states.NEXT ); + } + + /** + * Gets the number of equations for a specified level. + * @param level + * @returns {number} + * @public + */ + getNumberOfEquations( level ) { + return GameFactory.getNumberOfEquations( level ); + } + + /** + * Gets the number of points in a perfect score for a specified level. + * A perfect score is obtained when the user balances every equation correctly on the first attempt. + * @param level + * @returns {number} + * @public + */ + getPerfectScore( level ) { + return this.getNumberOfEquations( level ) * POINTS_FIRST_ATTEMPT; + } + + /** + * Is the current score a perfect score? + * This can be called at any time during the game, but can't possibly + * return true until the game has been completed. + * @returns {boolean} + * @public + */ + isPerfectScore() { + return this.pointsProperty.get() === this.getPerfectScore( this.levelProperty.get() ); + } + + /** + * Called when the user presses the "Start Over" button. + * @public + */ + newGame() { + this.stateProperty.set( this.states.LEVEL_SELECTION ); + this.timer.restart(); + } + + /** + * Called when the user presses the "Next" button. + * @public + */ + next() { + if ( this.currentEquationIndexProperty.get() < this.equations.length - 1 ) { + this.attempts = 0; + this.currentPoints = 0; + this.balancedRepresentation = BALANCED_REPRESENTATION_STRATEGIES[ this.levelProperty.get() ](); + this.currentEquationIndexProperty.set( this.currentEquationIndexProperty.get() + 1 ); + this.currentEquationProperty.set( this.equations[ this.currentEquationIndexProperty.get() ] ); + this.stateProperty.set( this.states.CHECK ); + } + else { + this.stateProperty.set( this.states.LEVEL_COMPLETED ); } } +} - return balancingChemicalEquations.register( 'GameModel', GameModel ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'GameModel', GameModel ); +export default GameModel; \ No newline at end of file diff --git a/js/game/model/RandomStrategy.js b/js/game/model/RandomStrategy.js index 10ab97f9..bb5b8718 100644 --- a/js/game/model/RandomStrategy.js +++ b/js/game/model/RandomStrategy.js @@ -6,116 +6,113 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEQueryParameters = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEQueryParameters' ); - const DisplacementEquation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/DisplacementEquation' ); - const merge = require( 'PHET_CORE/merge' ); - - class RandomStrategy { - - /** - * @param {function[]} pool - which equation pool to use - * @param {boolean} firstBigMolecule - specifies whether it's OK if the first equation in the set contains a "big" molecule - * @param {Object} [options] - */ - constructor( pool, firstBigMolecule, options ) { - - options = merge( { - exclusions: {} // see LEVEL3_EXCLUSIONS for doc - }, options ); - - this.pool = pool; - this.firstBigMolecule = firstBigMolecule; // can the first equation in the set contain a "big" molecule? - this.exclusions = options.exclusions; - } - /** - * Randomly selects a specified number of Equation factory functions from the pool. - * @private - * @param {number} numberOfEquations - * @returns { [{function}] } array of Equation factory functions - * @public - */ - getEquationFactoryFunctions( numberOfEquations ) { +import merge from '../../../../phet-core/js/merge.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEQueryParameters from '../../common/BCEQueryParameters.js'; +import DisplacementEquation from '../../common/model/DisplacementEquation.js'; + +class RandomStrategy { + + /** + * @param {function[]} pool - which equation pool to use + * @param {boolean} firstBigMolecule - specifies whether it's OK if the first equation in the set contains a "big" molecule + * @param {Object} [options] + */ + constructor( pool, firstBigMolecule, options ) { + + options = merge( { + exclusions: {} // see LEVEL3_EXCLUSIONS for doc + }, options ); + + this.pool = pool; + this.firstBigMolecule = firstBigMolecule; // can the first equation in the set contain a "big" molecule? + this.exclusions = options.exclusions; + } + + /** + * Randomly selects a specified number of Equation factory functions from the pool. + * @private + * @param {number} numberOfEquations + * @returns { [{function}] } array of Equation factory functions + * @public + */ + getEquationFactoryFunctions( numberOfEquations ) { - BCEQueryParameters.log && console.log( 'GameFactory: choosing challenges...' ); + BCEQueryParameters.log && console.log( 'GameFactory: choosing challenges...' ); - // operate on a copy of the pool, so that we can prune the pool as we select equations - const poolCopy = _.clone( this.pool ); + // operate on a copy of the pool, so that we can prune the pool as we select equations + const poolCopy = _.clone( this.pool ); - const factoryFunctions = []; - for ( let i = 0; i < numberOfEquations; i++ ) { + const factoryFunctions = []; + for ( let i = 0; i < numberOfEquations; i++ ) { - assert && assert( poolCopy.length > 0 ); + assert && assert( poolCopy.length > 0 ); - // randomly select an equation - const randomIndex = phet.joist.random.nextInt( poolCopy.length ); - let factoryFunction = poolCopy[ randomIndex ]; + // randomly select an equation + const randomIndex = phet.joist.random.nextInt( poolCopy.length ); + let factoryFunction = poolCopy[ randomIndex ]; - // If the first equation isn't supposed to contain any "big" molecules, - // then find an equation in the pool that has no big molecules. - if ( i === 0 && !this.firstBigMolecule && factoryFunction().hasBigMolecule() ) { + // If the first equation isn't supposed to contain any "big" molecules, + // then find an equation in the pool that has no big molecules. + if ( i === 0 && !this.firstBigMolecule && factoryFunction().hasBigMolecule() ) { - // start the search at a random index - const startIndex = phet.joist.random.nextInt( poolCopy.length ); - let index = startIndex; - let done = false; - while ( !done ) { + // start the search at a random index + const startIndex = phet.joist.random.nextInt( poolCopy.length ); + let index = startIndex; + let done = false; + while ( !done ) { - // next equation in the pool - factoryFunction = poolCopy.get( index ); + // next equation in the pool + factoryFunction = poolCopy.get( index ); - if ( !factoryFunction().hasBigMolecule() ) { - done = true; // success, this equation has no big molecules + if ( !factoryFunction().hasBigMolecule() ) { + done = true; // success, this equation has no big molecules + } + else { + // increment index to point to next in pool + index++; + if ( index > poolCopy.size() - 1 ) { + index = 0; } - else { - // increment index to point to next in pool - index++; - if ( index > poolCopy.size() - 1 ) { - index = 0; - } - - // give up if we've examined all equations in the pool - if ( index === startIndex ) { - done = true; - throw new Error( 'first equation contains big molecules because we ran out of equations' ); - } + + // give up if we've examined all equations in the pool + if ( index === startIndex ) { + done = true; + throw new Error( 'first equation contains big molecules because we ran out of equations' ); } } } + } - // add the equation to the game - factoryFunctions.push( factoryFunction ); - BCEQueryParameters.log && console.log( '+ chose ' + factoryFunction().toString() ); - - // remove the equation from the pool so it won't be selected again - poolCopy.splice( poolCopy.indexOf( factoryFunction ), 1 ); - - // if the selected equation has exclusions, remove them from the pool - for ( const functionName in this.exclusions ) { - if ( DisplacementEquation[ functionName ] === factoryFunction ) { - const excludedFunctions = this.exclusions[ functionName ]; - for ( let j = 0; j < excludedFunctions.length; j++ ) { - const excludedFunction = excludedFunctions[ j ]; - const excludedIndex = poolCopy.indexOf( excludedFunction ); - if ( excludedIndex !== -1 ) { - poolCopy.splice( excludedIndex, 1 ); - BCEQueryParameters.log && console.log( '- excluded ' + excludedFunction().toString() ); - } + // add the equation to the game + factoryFunctions.push( factoryFunction ); + BCEQueryParameters.log && console.log( '+ chose ' + factoryFunction().toString() ); + + // remove the equation from the pool so it won't be selected again + poolCopy.splice( poolCopy.indexOf( factoryFunction ), 1 ); + + // if the selected equation has exclusions, remove them from the pool + for ( const functionName in this.exclusions ) { + if ( DisplacementEquation[ functionName ] === factoryFunction ) { + const excludedFunctions = this.exclusions[ functionName ]; + for ( let j = 0; j < excludedFunctions.length; j++ ) { + const excludedFunction = excludedFunctions[ j ]; + const excludedIndex = poolCopy.indexOf( excludedFunction ); + if ( excludedIndex !== -1 ) { + poolCopy.splice( excludedIndex, 1 ); + BCEQueryParameters.log && console.log( '- excluded ' + excludedFunction().toString() ); } - break; // assumes that all exclusions are in 1 entry } + break; // assumes that all exclusions are in 1 entry } } - - assert && assert( factoryFunctions.length === numberOfEquations ); - return factoryFunctions; } + + assert && assert( factoryFunctions.length === numberOfEquations ); + return factoryFunctions; } +} - return balancingChemicalEquations.register( 'RandomStrategy', RandomStrategy ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'RandomStrategy', RandomStrategy ); +export default RandomStrategy; \ No newline at end of file diff --git a/js/game/view/BCERewardNode.js b/js/game/view/BCERewardNode.js index b32cd4e7..ff197275 100644 --- a/js/game/view/BCERewardNode.js +++ b/js/game/view/BCERewardNode.js @@ -5,130 +5,127 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const AtomNode = require( 'NITROGLYCERIN/nodes/AtomNode' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const Element = require( 'NITROGLYCERIN/Element' ); - const FaceNode = require( 'SCENERY_PHET/FaceNode' ); - const RewardNode = require( 'VEGAS/RewardNode' ); - const StarNode = require( 'SCENERY_PHET/StarNode' ); +import Element from '../../../../nitroglycerin/js/Element.js'; +import AtomNode from '../../../../nitroglycerin/js/nodes/AtomNode.js'; +import C2H2Node from '../../../../nitroglycerin/js/nodes/C2H2Node.js'; +import C2H4Node from '../../../../nitroglycerin/js/nodes/C2H4Node.js'; +import C2H5ClNode from '../../../../nitroglycerin/js/nodes/C2H5ClNode.js'; +import C2H5OHNode from '../../../../nitroglycerin/js/nodes/C2H5OHNode.js'; +import C2H6Node from '../../../../nitroglycerin/js/nodes/C2H6Node.js'; +import CH2ONode from '../../../../nitroglycerin/js/nodes/CH2ONode.js'; +import CH3OHNode from '../../../../nitroglycerin/js/nodes/CH3OHNode.js'; +import CH4Node from '../../../../nitroglycerin/js/nodes/CH4Node.js'; +import Cl2Node from '../../../../nitroglycerin/js/nodes/Cl2Node.js'; +import CNode from '../../../../nitroglycerin/js/nodes/CNode.js'; +import CO2Node from '../../../../nitroglycerin/js/nodes/CO2Node.js'; +import CONode from '../../../../nitroglycerin/js/nodes/CONode.js'; +import CS2Node from '../../../../nitroglycerin/js/nodes/CS2Node.js'; +import F2Node from '../../../../nitroglycerin/js/nodes/F2Node.js'; +import H2Node from '../../../../nitroglycerin/js/nodes/H2Node.js'; +import H2ONode from '../../../../nitroglycerin/js/nodes/H2ONode.js'; +import H2SNode from '../../../../nitroglycerin/js/nodes/H2SNode.js'; +import HClNode from '../../../../nitroglycerin/js/nodes/HClNode.js'; +import HFNode from '../../../../nitroglycerin/js/nodes/HFNode.js'; +import N2Node from '../../../../nitroglycerin/js/nodes/N2Node.js'; +import N2ONode from '../../../../nitroglycerin/js/nodes/N2ONode.js'; +import NH3Node from '../../../../nitroglycerin/js/nodes/NH3Node.js'; +import NO2Node from '../../../../nitroglycerin/js/nodes/NO2Node.js'; +import NONode from '../../../../nitroglycerin/js/nodes/NONode.js'; +import O2Node from '../../../../nitroglycerin/js/nodes/O2Node.js'; +import OF2Node from '../../../../nitroglycerin/js/nodes/OF2Node.js'; +import P4Node from '../../../../nitroglycerin/js/nodes/P4Node.js'; +import PCl3Node from '../../../../nitroglycerin/js/nodes/PCl3Node.js'; +import PCl5Node from '../../../../nitroglycerin/js/nodes/PCl5Node.js'; +import PF3Node from '../../../../nitroglycerin/js/nodes/PF3Node.js'; +import PH3Node from '../../../../nitroglycerin/js/nodes/PH3Node.js'; +import SNode from '../../../../nitroglycerin/js/nodes/SNode.js'; +import SO2Node from '../../../../nitroglycerin/js/nodes/SO2Node.js'; +import SO3Node from '../../../../nitroglycerin/js/nodes/SO3Node.js'; +import FaceNode from '../../../../scenery-phet/js/FaceNode.js'; +import StarNode from '../../../../scenery-phet/js/StarNode.js'; +import RewardNode from '../../../../vegas/js/RewardNode.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../../common/BCEConstants.js'; - // modules - molecules - const C2H2Node = require( 'NITROGLYCERIN/nodes/C2H2Node' ); - const C2H4Node = require( 'NITROGLYCERIN/nodes/C2H4Node' ); - const C2H5ClNode = require( 'NITROGLYCERIN/nodes/C2H5ClNode' ); - const C2H5OHNode = require( 'NITROGLYCERIN/nodes/C2H5OHNode' ); - const C2H6Node = require( 'NITROGLYCERIN/nodes/C2H6Node' ); - const CH2ONode = require( 'NITROGLYCERIN/nodes/CH2ONode' ); - const CH3OHNode = require( 'NITROGLYCERIN/nodes/CH3OHNode' ); - const CH4Node = require( 'NITROGLYCERIN/nodes/CH4Node' ); - const Cl2Node = require( 'NITROGLYCERIN/nodes/Cl2Node' ); - const CNode = require( 'NITROGLYCERIN/nodes/CNode' ); - const CO2Node = require( 'NITROGLYCERIN/nodes/CO2Node' ); - const CONode = require( 'NITROGLYCERIN/nodes/CONode' ); - const CS2Node = require( 'NITROGLYCERIN/nodes/CS2Node' ); - const F2Node = require( 'NITROGLYCERIN/nodes/F2Node' ); - const H2Node = require( 'NITROGLYCERIN/nodes/H2Node' ); - const H2ONode = require( 'NITROGLYCERIN/nodes/H2ONode' ); - const H2SNode = require( 'NITROGLYCERIN/nodes/H2SNode' ); - const HClNode = require( 'NITROGLYCERIN/nodes/HClNode' ); - const HFNode = require( 'NITROGLYCERIN/nodes/HFNode' ); - const N2Node = require( 'NITROGLYCERIN/nodes/N2Node' ); - const N2ONode = require( 'NITROGLYCERIN/nodes/N2ONode' ); - const NH3Node = require( 'NITROGLYCERIN/nodes/NH3Node' ); - const NO2Node = require( 'NITROGLYCERIN/nodes/NO2Node' ); - const NONode = require( 'NITROGLYCERIN/nodes/NONode' ); - const O2Node = require( 'NITROGLYCERIN/nodes/O2Node' ); - const OF2Node = require( 'NITROGLYCERIN/nodes/OF2Node' ); - const P4Node = require( 'NITROGLYCERIN/nodes/P4Node' ); - const PCl3Node = require( 'NITROGLYCERIN/nodes/PCl3Node' ); - const PCl5Node = require( 'NITROGLYCERIN/nodes/PCl5Node' ); - const PF3Node = require( 'NITROGLYCERIN/nodes/PF3Node' ); - const PH3Node = require( 'NITROGLYCERIN/nodes/PH3Node' ); - const SNode = require( 'NITROGLYCERIN/nodes/SNode' ); - const SO2Node = require( 'NITROGLYCERIN/nodes/SO2Node' ); - const SO3Node = require( 'NITROGLYCERIN/nodes/SO3Node' ); +// modules - molecules - // constants - const NUMBER_OF_NODES = 150; - const ATOM_OPTIONS = BCEConstants.ATOM_OPTIONS; - const MOLECULE_OPTIONS = { atomOptions: BCEConstants.ATOM_OPTIONS }; +// constants +const NUMBER_OF_NODES = 150; +const ATOM_OPTIONS = BCEConstants.ATOM_OPTIONS; +const MOLECULE_OPTIONS = { atomOptions: BCEConstants.ATOM_OPTIONS }; - // nodes used in reward, indexed by game level - const NODES = [ +// nodes used in reward, indexed by game level +const NODES = [ - // level 1: atoms - [ - new AtomNode( Element.C, ATOM_OPTIONS ), - new AtomNode( Element.Cl, ATOM_OPTIONS ), - new AtomNode( Element.F, ATOM_OPTIONS ), - new AtomNode( Element.H, ATOM_OPTIONS ), - new AtomNode( Element.N, ATOM_OPTIONS ), - new AtomNode( Element.O, ATOM_OPTIONS ), - new AtomNode( Element.P, ATOM_OPTIONS ), - new AtomNode( Element.S, ATOM_OPTIONS ) - ], + // level 1: atoms + [ + new AtomNode( Element.C, ATOM_OPTIONS ), + new AtomNode( Element.Cl, ATOM_OPTIONS ), + new AtomNode( Element.F, ATOM_OPTIONS ), + new AtomNode( Element.H, ATOM_OPTIONS ), + new AtomNode( Element.N, ATOM_OPTIONS ), + new AtomNode( Element.O, ATOM_OPTIONS ), + new AtomNode( Element.P, ATOM_OPTIONS ), + new AtomNode( Element.S, ATOM_OPTIONS ) + ], - // level 2: molecules - [ - new CNode( MOLECULE_OPTIONS ), - new C2H2Node( MOLECULE_OPTIONS ), - new C2H4Node( MOLECULE_OPTIONS ), - new C2H5ClNode( MOLECULE_OPTIONS ), - new C2H5OHNode( MOLECULE_OPTIONS ), - new C2H6Node( MOLECULE_OPTIONS ), - new CH2ONode( MOLECULE_OPTIONS ), - new CH3OHNode( MOLECULE_OPTIONS ), - new CH4Node( MOLECULE_OPTIONS ), - new Cl2Node( MOLECULE_OPTIONS ), - new CONode( MOLECULE_OPTIONS ), - new CO2Node( MOLECULE_OPTIONS ), - new CS2Node( MOLECULE_OPTIONS ), - new F2Node( MOLECULE_OPTIONS ), - new CONode( MOLECULE_OPTIONS ), - new H2Node( MOLECULE_OPTIONS ), - new H2ONode( MOLECULE_OPTIONS ), - new H2SNode( MOLECULE_OPTIONS ), - new HClNode( MOLECULE_OPTIONS ), - new HFNode( MOLECULE_OPTIONS ), - new N2Node( MOLECULE_OPTIONS ), - new N2ONode( MOLECULE_OPTIONS ), - new NH3Node( MOLECULE_OPTIONS ), - new NONode( MOLECULE_OPTIONS ), - new NO2Node( MOLECULE_OPTIONS ), - new O2Node( MOLECULE_OPTIONS ), - new OF2Node( MOLECULE_OPTIONS ), - new P4Node( MOLECULE_OPTIONS ), - new PCl3Node( MOLECULE_OPTIONS ), - new PCl5Node( MOLECULE_OPTIONS ), - new PH3Node( MOLECULE_OPTIONS ), - new PF3Node( MOLECULE_OPTIONS ), - new SNode( MOLECULE_OPTIONS ), - new SO2Node( MOLECULE_OPTIONS ), - new SO3Node( MOLECULE_OPTIONS ) - ], + // level 2: molecules + [ + new CNode( MOLECULE_OPTIONS ), + new C2H2Node( MOLECULE_OPTIONS ), + new C2H4Node( MOLECULE_OPTIONS ), + new C2H5ClNode( MOLECULE_OPTIONS ), + new C2H5OHNode( MOLECULE_OPTIONS ), + new C2H6Node( MOLECULE_OPTIONS ), + new CH2ONode( MOLECULE_OPTIONS ), + new CH3OHNode( MOLECULE_OPTIONS ), + new CH4Node( MOLECULE_OPTIONS ), + new Cl2Node( MOLECULE_OPTIONS ), + new CONode( MOLECULE_OPTIONS ), + new CO2Node( MOLECULE_OPTIONS ), + new CS2Node( MOLECULE_OPTIONS ), + new F2Node( MOLECULE_OPTIONS ), + new CONode( MOLECULE_OPTIONS ), + new H2Node( MOLECULE_OPTIONS ), + new H2ONode( MOLECULE_OPTIONS ), + new H2SNode( MOLECULE_OPTIONS ), + new HClNode( MOLECULE_OPTIONS ), + new HFNode( MOLECULE_OPTIONS ), + new N2Node( MOLECULE_OPTIONS ), + new N2ONode( MOLECULE_OPTIONS ), + new NH3Node( MOLECULE_OPTIONS ), + new NONode( MOLECULE_OPTIONS ), + new NO2Node( MOLECULE_OPTIONS ), + new O2Node( MOLECULE_OPTIONS ), + new OF2Node( MOLECULE_OPTIONS ), + new P4Node( MOLECULE_OPTIONS ), + new PCl3Node( MOLECULE_OPTIONS ), + new PCl5Node( MOLECULE_OPTIONS ), + new PH3Node( MOLECULE_OPTIONS ), + new PF3Node( MOLECULE_OPTIONS ), + new SNode( MOLECULE_OPTIONS ), + new SO2Node( MOLECULE_OPTIONS ), + new SO3Node( MOLECULE_OPTIONS ) + ], - // level 3: faces and stars - [ - new FaceNode( 40, { headStroke: 'black' } ), - new StarNode() - ] - ]; + // level 3: faces and stars + [ + new FaceNode( 40, { headStroke: 'black' } ), + new StarNode() + ] +]; - class BCERewardNode extends RewardNode { +class BCERewardNode extends RewardNode { - /** - * @param {number} level game level - */ - constructor( level ) { - assert && assert( level >= 0 && level < NODES.length ); - super( { nodes: RewardNode.createRandomNodes( NODES[ level ], NUMBER_OF_NODES ) } ); - } + /** + * @param {number} level game level + */ + constructor( level ) { + assert && assert( level >= 0 && level < NODES.length ); + super( { nodes: RewardNode.createRandomNodes( NODES[ level ], NUMBER_OF_NODES ) } ); } +} - return balancingChemicalEquations.register( 'BCERewardNode', BCERewardNode ); -} ); +balancingChemicalEquations.register( 'BCERewardNode', BCERewardNode ); +export default BCERewardNode; \ No newline at end of file diff --git a/js/game/view/GameFeedbackDialog.js b/js/game/view/GameFeedbackDialog.js index e5887394..e5f92b32 100644 --- a/js/game/view/GameFeedbackDialog.js +++ b/js/game/view/GameFeedbackDialog.js @@ -6,303 +6,300 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - - // modules - const BalancedRepresentation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/BalancedRepresentation' ); - const BalanceScalesNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BalanceScalesNode' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BarChartsNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BarChartsNode' ); - const FaceNode = require( 'SCENERY_PHET/FaceNode' ); - const FontAwesomeNode = require( 'SUN/FontAwesomeNode' ); - const HBox = require( 'SCENERY/nodes/HBox' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const Panel = require( 'SUN/Panel' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Property = require( 'AXON/Property' ); - const Rectangle = require( 'SCENERY/nodes/Rectangle' ); - const StringUtils = require( 'PHETCOMMON/util/StringUtils' ); - const Text = require( 'SCENERY/nodes/Text' ); - const TextPushButton = require( 'SUN/buttons/TextPushButton' ); - const VBox = require( 'SCENERY/nodes/VBox' ); - const VStrut = require( 'SCENERY/nodes/VStrut' ); - - // strings - const balancedString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/balanced' ); - const hideWhyString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/hideWhy' ); - const nextString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/next' ); - const notBalancedString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/notBalanced' ); - const notSimplifiedString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/notSimplified' ); - const pattern0PointsString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/pattern_0points' ); - const showAnswerString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/showAnswer' ); - const showWhyString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/showWhy' ); - const tryAgainString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/tryAgain' ); - - // constants - const TEXT_FONT = new PhetFont( 18 ); - const STATE_BUTTON_FONT = new PhetFont( 20 ); - const STATE_BUTTON_FILL = 'yellow'; - const WHY_BUTTON_FONT = new PhetFont( 16 ); - const WHY_BUTTON_FILL = '#d9d9d9'; - const ACTION_AREA_Y_SPACING = 8; // vertical space that separates the 'action area' (buttons) from stuff above it - - class GameFeedbackDialog extends Node { - - /** - * @param {GameModel} model - * @param {HorizontalAligner} aligner - * @param {Object} [options] - */ - constructor( model, aligner, options ) { - - options = merge( { - fill: '#c1d8fe', - xMargin: 40, - yMargin: 10, - cornerRadius: 0, - hBoxSpacing: 5, - vBoxSpacing: 7, - shadowXOffset: 5, - shadowYOffset: 5 - }, options ); - - const equation = model.currentEquationProperty.get(); - const balancedRepresentation = model.balancedRepresentation; - const points = model.currentPoints; - const maxWidth = 0.75 * aligner.getScreenWidth(); // max width of UI elements - const textOptions = { font: TEXT_FONT, maxWidth: maxWidth }; - - // happy/sad face - const faceNode = new FaceNode( 75 ); - if ( !equation.balancedProperty.get() ) { faceNode.frown(); } - - let content; - if ( equation.balancedAndSimplified ) { - - // balanced and simplified - content = new VBox( { - children: [ - - // happy face - faceNode, - - // check mark + 'balanced' - new HBox( { - children: [ createCorrectIcon(), new Text( balancedString, textOptions ) ], - spacing: options.hBoxSpacing - } ), - - // points awarded - new Text( StringUtils.format( pattern0PointsString, points ), { - font: new PhetFont( { - size: 24, - weight: 'bold' - } ), maxWidth: maxWidth - } ), - - // space - new VStrut( ACTION_AREA_Y_SPACING ), - - // Next button - createStateChangeButton( nextString, model.next.bind( model ), maxWidth ) - ], - spacing: options.vBoxSpacing - } ); - } - else if ( equation.balancedProperty.get() ) { - - // balanced, not simplified: happy face with 'balance' and 'not simplified' below it - content = new VBox( { - children: [ - - // happy face - faceNode, - new VBox( { - align: 'left', - spacing: 3, - children: [ - - // check mark + 'balanced' - new HBox( { - children: [ createCorrectIcon(), new Text( balancedString, textOptions ) ], - spacing: options.hBoxSpacing - } ), - - // red X + 'not simplified' - new HBox( { - children: [ createIncorrectIcon(), new Text( notSimplifiedString, textOptions ) ], - spacing: options.hBoxSpacing - } ) - ] - } ), - - // space - new VStrut( ACTION_AREA_Y_SPACING ), - - // Try Again or Show Answer button - createButtonForState( model, maxWidth ) - ], - spacing: options.vBoxSpacing - } ); - } - else { - - // not balanced - let saveCenterX; // saves the dialog's centerX when pressing Show/Hide Why. - let balancedRepresentationNode = null; // create on demand - - // 'Show Why' button, exposes one of the 'balanced' representations to explain why it's not balanced - var showWhyButton = new TextPushButton( showWhyString, { - listener: () => { - showWhyButton.visible = false; - hideWhyButton.visible = true; - saveCenterX = this.centerX; - if ( !balancedRepresentationNode ) { - balancedRepresentationNode = createBalancedRepresentation( equation, balancedRepresentation, aligner ); - } - content.addChild( balancedRepresentationNode ); - this.centerX = saveCenterX; - }, - font: WHY_BUTTON_FONT, - baseColor: WHY_BUTTON_FILL, - visible: true, - maxWidth: maxWidth - } ); - - // 'Hide Why' button, hides the 'balanced' representation - var hideWhyButton = new TextPushButton( hideWhyString, { - listener: () => { - showWhyButton.visible = true; - hideWhyButton.visible = false; - saveCenterX = this.centerX; - content.removeChild( balancedRepresentationNode ); - this.centerX = saveCenterX; - }, - font: WHY_BUTTON_FONT, - baseColor: WHY_BUTTON_FILL, - visible: !showWhyButton.visible, - center: showWhyButton.center, - maxWidth: maxWidth - } ); - - content = new VBox( { - children: [ - - // sad face - faceNode, - - // red X + 'not balanced' - new HBox( { - children: [ createIncorrectIcon(), new Text( notBalancedString, textOptions ) ], - spacing: options.hBoxSpacing - } ), - - // space - new VStrut( ACTION_AREA_Y_SPACING ), - - // Try Again or Show Answer button - createButtonForState( model, maxWidth ), - - // Show/Hide Why buttons - new Node( { children: [ showWhyButton, hideWhyButton ] } ) - ], - spacing: options.vBoxSpacing - } ); - } - - // panel, which will resize dynamically - const panel = new Panel( content, options ); - - // shadow - const shadowNode = new Rectangle( 0, 0, 1, 1, { - fill: 'rgba( 80, 80, 80, 0.12 )', - cornerRadius: options.cornerRadius - } ); - const updateShadow = () => { - shadowNode.setRect( panel.left + options.shadowXOffset, panel.top + options.shadowYOffset, panel.width, panel.height ); - }; - content.on( 'bounds', updateShadow ); // resize shadow when panel changes size - updateShadow(); - - options.children = [ shadowNode, panel ]; - super( options ); - } - } - /** - * Creates the icon for a correct answer, a green check mark. - * @returns {Node} - */ - function createCorrectIcon() { - return new FontAwesomeNode( 'check', { fill: 'rgb( 0, 180, 0 )' } ); - } +import Property from '../../../../axon/js/Property.js'; +import merge from '../../../../phet-core/js/merge.js'; +import StringUtils from '../../../../phetcommon/js/util/StringUtils.js'; +import FaceNode from '../../../../scenery-phet/js/FaceNode.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import HBox from '../../../../scenery/js/nodes/HBox.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Rectangle from '../../../../scenery/js/nodes/Rectangle.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import VBox from '../../../../scenery/js/nodes/VBox.js'; +import VStrut from '../../../../scenery/js/nodes/VStrut.js'; +import TextPushButton from '../../../../sun/js/buttons/TextPushButton.js'; +import FontAwesomeNode from '../../../../sun/js/FontAwesomeNode.js'; +import Panel from '../../../../sun/js/Panel.js'; +import balancingChemicalEquationsStrings from '../../balancing-chemical-equations-strings.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BalancedRepresentation from '../../common/model/BalancedRepresentation.js'; +import BalanceScalesNode from '../../common/view/BalanceScalesNode.js'; +import BarChartsNode from '../../common/view/BarChartsNode.js'; + +const balancedString = balancingChemicalEquationsStrings.balanced; +const hideWhyString = balancingChemicalEquationsStrings.hideWhy; +const nextString = balancingChemicalEquationsStrings.next; +const notBalancedString = balancingChemicalEquationsStrings.notBalanced; +const notSimplifiedString = balancingChemicalEquationsStrings.notSimplified; +const pattern0PointsString = balancingChemicalEquationsStrings.pattern_0points; +const showAnswerString = balancingChemicalEquationsStrings.showAnswer; +const showWhyString = balancingChemicalEquationsStrings.showWhy; +const tryAgainString = balancingChemicalEquationsStrings.tryAgain; + +// constants +const TEXT_FONT = new PhetFont( 18 ); +const STATE_BUTTON_FONT = new PhetFont( 20 ); +const STATE_BUTTON_FILL = 'yellow'; +const WHY_BUTTON_FONT = new PhetFont( 16 ); +const WHY_BUTTON_FILL = '#d9d9d9'; +const ACTION_AREA_Y_SPACING = 8; // vertical space that separates the 'action area' (buttons) from stuff above it + +class GameFeedbackDialog extends Node { /** - * Creates the icon for an incorrect answer, a red 'X'. - * @returns {Node} + * @param {GameModel} model + * @param {HorizontalAligner} aligner + * @param {Object} [options] */ - function createIncorrectIcon() { - return new FontAwesomeNode( 'times', { fill: 'rgb(252,104,0)' } ); - } + constructor( model, aligner, options ) { + + options = merge( { + fill: '#c1d8fe', + xMargin: 40, + yMargin: 10, + cornerRadius: 0, + hBoxSpacing: 5, + vBoxSpacing: 7, + shadowXOffset: 5, + shadowYOffset: 5 + }, options ); + + const equation = model.currentEquationProperty.get(); + const balancedRepresentation = model.balancedRepresentation; + const points = model.currentPoints; + const maxWidth = 0.75 * aligner.getScreenWidth(); // max width of UI elements + const textOptions = { font: TEXT_FONT, maxWidth: maxWidth }; + + // happy/sad face + const faceNode = new FaceNode( 75 ); + if ( !equation.balancedProperty.get() ) { faceNode.frown(); } + + let content; + if ( equation.balancedAndSimplified ) { + + // balanced and simplified + content = new VBox( { + children: [ + + // happy face + faceNode, + + // check mark + 'balanced' + new HBox( { + children: [ createCorrectIcon(), new Text( balancedString, textOptions ) ], + spacing: options.hBoxSpacing + } ), + + // points awarded + new Text( StringUtils.format( pattern0PointsString, points ), { + font: new PhetFont( { + size: 24, + weight: 'bold' + } ), maxWidth: maxWidth + } ), + + // space + new VStrut( ACTION_AREA_Y_SPACING ), + + // Next button + createStateChangeButton( nextString, model.next.bind( model ), maxWidth ) + ], + spacing: options.vBoxSpacing + } ); + } + else if ( equation.balancedProperty.get() ) { + + // balanced, not simplified: happy face with 'balance' and 'not simplified' below it + content = new VBox( { + children: [ + + // happy face + faceNode, + new VBox( { + align: 'left', + spacing: 3, + children: [ + + // check mark + 'balanced' + new HBox( { + children: [ createCorrectIcon(), new Text( balancedString, textOptions ) ], + spacing: options.hBoxSpacing + } ), + + // red X + 'not simplified' + new HBox( { + children: [ createIncorrectIcon(), new Text( notSimplifiedString, textOptions ) ], + spacing: options.hBoxSpacing + } ) + ] + } ), + + // space + new VStrut( ACTION_AREA_Y_SPACING ), + + // Try Again or Show Answer button + createButtonForState( model, maxWidth ) + ], + spacing: options.vBoxSpacing + } ); + } + else { - /** - * Creates a text button that performs a model state change when pressed. - * @param {string} label - * @param {function} modelFunction model function that performs the state change - * @param {number} maxWidth - * @returns {TextPushButton} - */ - function createStateChangeButton( label, modelFunction, maxWidth ) { - return new TextPushButton( label, { - font: STATE_BUTTON_FONT, - baseColor: STATE_BUTTON_FILL, - maxWidth: maxWidth, - listener: function() { - modelFunction(); - } + // not balanced + let saveCenterX; // saves the dialog's centerX when pressing Show/Hide Why. + let balancedRepresentationNode = null; // create on demand + + // 'Show Why' button, exposes one of the 'balanced' representations to explain why it's not balanced + var showWhyButton = new TextPushButton( showWhyString, { + listener: () => { + showWhyButton.visible = false; + hideWhyButton.visible = true; + saveCenterX = this.centerX; + if ( !balancedRepresentationNode ) { + balancedRepresentationNode = createBalancedRepresentation( equation, balancedRepresentation, aligner ); + } + content.addChild( balancedRepresentationNode ); + this.centerX = saveCenterX; + }, + font: WHY_BUTTON_FONT, + baseColor: WHY_BUTTON_FILL, + visible: true, + maxWidth: maxWidth + } ); + + // 'Hide Why' button, hides the 'balanced' representation + var hideWhyButton = new TextPushButton( hideWhyString, { + listener: () => { + showWhyButton.visible = true; + hideWhyButton.visible = false; + saveCenterX = this.centerX; + content.removeChild( balancedRepresentationNode ); + this.centerX = saveCenterX; + }, + font: WHY_BUTTON_FONT, + baseColor: WHY_BUTTON_FILL, + visible: !showWhyButton.visible, + center: showWhyButton.center, + maxWidth: maxWidth + } ); + + content = new VBox( { + children: [ + + // sad face + faceNode, + + // red X + 'not balanced' + new HBox( { + children: [ createIncorrectIcon(), new Text( notBalancedString, textOptions ) ], + spacing: options.hBoxSpacing + } ), + + // space + new VStrut( ACTION_AREA_Y_SPACING ), + + // Try Again or Show Answer button + createButtonForState( model, maxWidth ), + + // Show/Hide Why buttons + new Node( { children: [ showWhyButton, hideWhyButton ] } ) + ], + spacing: options.vBoxSpacing + } ); + } + + // panel, which will resize dynamically + const panel = new Panel( content, options ); + + // shadow + const shadowNode = new Rectangle( 0, 0, 1, 1, { + fill: 'rgba( 80, 80, 80, 0.12 )', + cornerRadius: options.cornerRadius } ); + const updateShadow = () => { + shadowNode.setRect( panel.left + options.shadowXOffset, panel.top + options.shadowYOffset, panel.width, panel.height ); + }; + content.on( 'bounds', updateShadow ); // resize shadow when panel changes size + updateShadow(); + + options.children = [ shadowNode, panel ]; + super( options ); } +} - /** - * Creates a button that is appropriate for the current state of the model. - * @param {GameModel} model - * @param {number} maxWidth - * @returns {TextPushButton} - */ - function createButtonForState( model, maxWidth ) { - let button = null; - if ( model.stateProperty.get() === model.states.TRY_AGAIN ) { - button = createStateChangeButton( tryAgainString, model.tryAgain.bind( model ), maxWidth ); - } - else if ( model.stateProperty.get() === model.states.SHOW_ANSWER ) { - button = createStateChangeButton( showAnswerString, model.showAnswer.bind( model ), maxWidth ); +/** + * Creates the icon for a correct answer, a green check mark. + * @returns {Node} + */ +function createCorrectIcon() { + return new FontAwesomeNode( 'check', { fill: 'rgb( 0, 180, 0 )' } ); +} + +/** + * Creates the icon for an incorrect answer, a red 'X'. + * @returns {Node} + */ +function createIncorrectIcon() { + return new FontAwesomeNode( 'times', { fill: 'rgb(252,104,0)' } ); +} + +/** + * Creates a text button that performs a model state change when pressed. + * @param {string} label + * @param {function} modelFunction model function that performs the state change + * @param {number} maxWidth + * @returns {TextPushButton} + */ +function createStateChangeButton( label, modelFunction, maxWidth ) { + return new TextPushButton( label, { + font: STATE_BUTTON_FONT, + baseColor: STATE_BUTTON_FILL, + maxWidth: maxWidth, + listener: function() { + modelFunction(); } - return button; + } ); +} + +/** + * Creates a button that is appropriate for the current state of the model. + * @param {GameModel} model + * @param {number} maxWidth + * @returns {TextPushButton} + */ +function createButtonForState( model, maxWidth ) { + let button = null; + if ( model.stateProperty.get() === model.states.TRY_AGAIN ) { + button = createStateChangeButton( tryAgainString, model.tryAgain.bind( model ), maxWidth ); } + else if ( model.stateProperty.get() === model.states.SHOW_ANSWER ) { + button = createStateChangeButton( showAnswerString, model.showAnswer.bind( model ), maxWidth ); + } + return button; +} - /** - * Creates the representation of 'balanced' that becomes visible when the 'Show Why' button is pressed. - * @param {Equation} equation - * @param {BalancedRepresentation} balancedRepresentation - * @param {HorizontalAligner} aligner - * @returns {Node} - */ - function createBalancedRepresentation( equation, balancedRepresentation, aligner ) { - let balancedRepresentationNode; - if ( balancedRepresentation === BalancedRepresentation.BALANCE_SCALES ) { - balancedRepresentationNode = new BalanceScalesNode( new Property( equation ), aligner ); - } - else if ( balancedRepresentation === BalancedRepresentation.BAR_CHARTS ) { - balancedRepresentationNode = new BarChartsNode( new Property( equation ), aligner ); - } - else { - throw new Error( 'unsupported balancedRepresentation: ' + balancedRepresentation ); - } - balancedRepresentationNode.setScaleMagnitude( 0.65 ); // issue #29, shrink size so that it doesn't cover so much of the screen - return balancedRepresentationNode; +/** + * Creates the representation of 'balanced' that becomes visible when the 'Show Why' button is pressed. + * @param {Equation} equation + * @param {BalancedRepresentation} balancedRepresentation + * @param {HorizontalAligner} aligner + * @returns {Node} + */ +function createBalancedRepresentation( equation, balancedRepresentation, aligner ) { + let balancedRepresentationNode; + if ( balancedRepresentation === BalancedRepresentation.BALANCE_SCALES ) { + balancedRepresentationNode = new BalanceScalesNode( new Property( equation ), aligner ); + } + else if ( balancedRepresentation === BalancedRepresentation.BAR_CHARTS ) { + balancedRepresentationNode = new BarChartsNode( new Property( equation ), aligner ); + } + else { + throw new Error( 'unsupported balancedRepresentation: ' + balancedRepresentation ); } + balancedRepresentationNode.setScaleMagnitude( 0.65 ); // issue #29, shrink size so that it doesn't cover so much of the screen + return balancedRepresentationNode; +} - return balancingChemicalEquations.register( 'GameFeedbackDialog', GameFeedbackDialog ); -} ); +balancingChemicalEquations.register( 'GameFeedbackDialog', GameFeedbackDialog ); +export default GameFeedbackDialog; \ No newline at end of file diff --git a/js/game/view/GamePlayNode.js b/js/game/view/GamePlayNode.js index 04f77aca..5044c49e 100644 --- a/js/game/view/GamePlayNode.js +++ b/js/game/view/GamePlayNode.js @@ -5,254 +5,251 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const BoxesNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BoxesNode' ); - const DerivedProperty = require( 'AXON/DerivedProperty' ); - const Dimension2 = require( 'DOT/Dimension2' ); - const EquationNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/EquationNode' ); - const FiniteStatusBar = require( 'VEGAS/FiniteStatusBar' ); - const GameFeedbackDialog = require( 'BALANCING_CHEMICAL_EQUATIONS/game/view/GameFeedbackDialog' ); - const HorizontalAligner = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/HorizontalAligner' ); - const merge = require( 'PHET_CORE/merge' ); - const Node = require( 'SCENERY/nodes/Node' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const ScoreDisplayLabeledNumber = require( 'VEGAS/ScoreDisplayLabeledNumber' ); - const Text = require( 'SCENERY/nodes/Text' ); - const TextPushButton = require( 'SUN/buttons/TextPushButton' ); - - // strings - const checkString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/check' ); - const nextString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/next' ); - - // constants - const BOX_SIZE = new Dimension2( 285, 340 ); - const BOX_X_SPACING = 140; // horizontal spacing between boxes - - class GamePlayNode extends Node { - - /** - * @param {GameModel} model - * @param {GameViewProperties} viewProperties - * @param {GameAudioPlayer} audioPlayer - * @param {Bounds2} layoutBounds layout bounds of the parent ScreenView - * @param {Property.} visibleBoundsProperty of the parent ScreenView - * @param {Object} [options] - */ - constructor( model, viewProperties, audioPlayer, layoutBounds, visibleBoundsProperty, options ) { - - super(); - - this.model = model; // @private - this.audioPlayer = audioPlayer; // @private - this.layoutBounds = layoutBounds; // @private - this.aligner = new HorizontalAligner( layoutBounds.width, BOX_SIZE.width, BOX_X_SPACING ); // @private - this.feedbackDialog = null; // @private game feedback dialog, created on demand - - // status bar - const statusBar = new FiniteStatusBar( layoutBounds, visibleBoundsProperty, model.pointsProperty, { - scoreDisplayConstructor: ScoreDisplayLabeledNumber, - - // FiniteStatusBar uses 1-based level numbering, model is 0-based, see #127. - levelProperty: new DerivedProperty( [ model.levelProperty ], level => level + 1 ), - challengeIndexProperty: model.currentEquationIndexProperty, - numberOfChallengesProperty: model.numberOfEquationsProperty, - elapsedTimeProperty: model.timer.elapsedTimeProperty, - timerEnabledProperty: viewProperties.timerEnabledProperty, - font: new PhetFont( 14 ), - textFill: 'white', - barFill: 'rgb( 49, 117, 202 )', - xMargin: 30, - yMargin: 5, - startOverButtonOptions: { - baseColor: 'rgb( 229, 243, 255 )', - textFill: 'black', - listener: this.model.newGame.bind( this.model ), - xMargin: 10, - yMargin: 5 - } +import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; +import Dimension2 from '../../../../dot/js/Dimension2.js'; +import merge from '../../../../phet-core/js/merge.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import TextPushButton from '../../../../sun/js/buttons/TextPushButton.js'; +import FiniteStatusBar from '../../../../vegas/js/FiniteStatusBar.js'; +import ScoreDisplayLabeledNumber from '../../../../vegas/js/ScoreDisplayLabeledNumber.js'; +import balancingChemicalEquationsStrings from '../../balancing-chemical-equations-strings.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../../common/BCEConstants.js'; +import BoxesNode from '../../common/view/BoxesNode.js'; +import EquationNode from '../../common/view/EquationNode.js'; +import HorizontalAligner from '../../common/view/HorizontalAligner.js'; +import GameFeedbackDialog from './GameFeedbackDialog.js'; + +const checkString = balancingChemicalEquationsStrings.check; +const nextString = balancingChemicalEquationsStrings.next; + +// constants +const BOX_SIZE = new Dimension2( 285, 340 ); +const BOX_X_SPACING = 140; // horizontal spacing between boxes + +class GamePlayNode extends Node { + + /** + * @param {GameModel} model + * @param {GameViewProperties} viewProperties + * @param {GameAudioPlayer} audioPlayer + * @param {Bounds2} layoutBounds layout bounds of the parent ScreenView + * @param {Property.} visibleBoundsProperty of the parent ScreenView + * @param {Object} [options] + */ + constructor( model, viewProperties, audioPlayer, layoutBounds, visibleBoundsProperty, options ) { + + super(); + + this.model = model; // @private + this.audioPlayer = audioPlayer; // @private + this.layoutBounds = layoutBounds; // @private + this.aligner = new HorizontalAligner( layoutBounds.width, BOX_SIZE.width, BOX_X_SPACING ); // @private + this.feedbackDialog = null; // @private game feedback dialog, created on demand + + // status bar + const statusBar = new FiniteStatusBar( layoutBounds, visibleBoundsProperty, model.pointsProperty, { + scoreDisplayConstructor: ScoreDisplayLabeledNumber, + + // FiniteStatusBar uses 1-based level numbering, model is 0-based, see #127. + levelProperty: new DerivedProperty( [ model.levelProperty ], level => level + 1 ), + challengeIndexProperty: model.currentEquationIndexProperty, + numberOfChallengesProperty: model.numberOfEquationsProperty, + elapsedTimeProperty: model.timer.elapsedTimeProperty, + timerEnabledProperty: viewProperties.timerEnabledProperty, + font: new PhetFont( 14 ), + textFill: 'white', + barFill: 'rgb( 49, 117, 202 )', + xMargin: 30, + yMargin: 5, + startOverButtonOptions: { + baseColor: 'rgb( 229, 243, 255 )', + textFill: 'black', + listener: this.model.newGame.bind( this.model ), + xMargin: 10, + yMargin: 5 + } + } ); + this.addChild( statusBar ); + + // @private boxes that show molecules corresponding to the equation coefficients + this.boxesNode = new BoxesNode( model.currentEquationProperty, model.COEFFICENTS_RANGE, this.aligner, + BOX_SIZE, BCEConstants.BOX_COLOR, viewProperties.reactantsBoxExpandedProperty, viewProperties.productsBoxExpandedProperty, + { y: statusBar.bottom + 15 } ); + this.addChild( this.boxesNode ); + + // @private equation + this.equationNode = new EquationNode( this.model.currentEquationProperty, this.model.COEFFICENTS_RANGE, this.aligner ); + this.addChild( this.equationNode ); + this.equationNode.centerY = this.layoutBounds.height - ( this.layoutBounds.height - this.boxesNode.bottom ) / 2; + + // buttons: Check, Next + const BUTTONS_OPTIONS = { + font: new PhetFont( 20 ), + baseColor: 'yellow', + centerX: 0, + bottom: this.boxesNode.bottom + }; + // @private + this.checkButton = new TextPushButton( checkString, merge( BUTTONS_OPTIONS, { + listener: () => { + this.playGuessAudio(); + this.model.check(); + } + } ) ); + // @private + this.nextButton = new TextPushButton( nextString, merge( BUTTONS_OPTIONS, { + listener: () => { + this.model.next(); + } + } ) ); + + // constrain buttons to fit the horizontal space between the boxes + const buttonsParent = new Node( { + maxWidth: 0.85 * BOX_X_SPACING, + children: [ this.checkButton, this.nextButton ] + } ); + buttonsParent.centerX = this.layoutBounds.centerX; + buttonsParent.bottom = this.boxesNode.bottom; + this.addChild( buttonsParent ); + + // developer stuff + if ( phet.chipper.queryParameters.showAnswers ) { + + // display correct coefficient at bottom center of the screen + const answerNode = new Text( '', { font: new PhetFont( 12 ), bottom: this.layoutBounds.bottom - 5 } ); + this.addChild( answerNode ); + this.model.currentEquationProperty.link( equation => { + answerNode.text = equation.getCoefficientsString(); + answerNode.centerX = this.layoutBounds.centerX; } ); - this.addChild( statusBar ); - - // @private boxes that show molecules corresponding to the equation coefficients - this.boxesNode = new BoxesNode( model.currentEquationProperty, model.COEFFICENTS_RANGE, this.aligner, - BOX_SIZE, BCEConstants.BOX_COLOR, viewProperties.reactantsBoxExpandedProperty, viewProperties.productsBoxExpandedProperty, - { y: statusBar.bottom + 15 } ); - this.addChild( this.boxesNode ); - // @private equation - this.equationNode = new EquationNode( this.model.currentEquationProperty, this.model.COEFFICENTS_RANGE, this.aligner ); - this.addChild( this.equationNode ); - this.equationNode.centerY = this.layoutBounds.height - ( this.layoutBounds.height - this.boxesNode.bottom ) / 2; - - // buttons: Check, Next - const BUTTONS_OPTIONS = { - font: new PhetFont( 20 ), - baseColor: 'yellow', - centerX: 0, - bottom: this.boxesNode.bottom - }; - // @private - this.checkButton = new TextPushButton( checkString, merge( BUTTONS_OPTIONS, { - listener: () => { - this.playGuessAudio(); - this.model.check(); - } - } ) ); - // @private - this.nextButton = new TextPushButton( nextString, merge( BUTTONS_OPTIONS, { - listener: () => { - this.model.next(); - } - } ) ); - - // constrain buttons to fit the horizontal space between the boxes - const buttonsParent = new Node( { - maxWidth: 0.85 * BOX_X_SPACING, - children: [ this.checkButton, this.nextButton ] + // skips the current equation + const skipButton = new TextPushButton( 'Skip', { + font: new PhetFont( 10 ), + baseColor: 'red', + textFill: 'white', + listener: model.next.bind( model ), // equivalent to 'Next' + left: this.layoutBounds.left + 4, + bottom: this.layoutBounds.bottom - 2 } ); - buttonsParent.centerX = this.layoutBounds.centerX; - buttonsParent.bottom = this.boxesNode.bottom; - this.addChild( buttonsParent ); - - // developer stuff - if ( phet.chipper.queryParameters.showAnswers ) { - - // display correct coefficient at bottom center of the screen - const answerNode = new Text( '', { font: new PhetFont( 12 ), bottom: this.layoutBounds.bottom - 5 } ); - this.addChild( answerNode ); - this.model.currentEquationProperty.link( equation => { - answerNode.text = equation.getCoefficientsString(); - answerNode.centerX = this.layoutBounds.centerX; - } ); + this.addChild( skipButton ); + } - // skips the current equation - const skipButton = new TextPushButton( 'Skip', { - font: new PhetFont( 10 ), - baseColor: 'red', - textFill: 'white', - listener: model.next.bind( model ), // equivalent to 'Next' - left: this.layoutBounds.left + 4, - bottom: this.layoutBounds.bottom - 2 - } ); - this.addChild( skipButton ); + // Call an initializer to set up the game for the state. + model.stateProperty.link( state => { + const states = model.states; + if ( state === states.CHECK ) { + this.initCheck(); } + else if ( state === states.TRY_AGAIN ) { + this.initTryAgain(); + } + else if ( state === states.SHOW_ANSWER ) { + this.initShowAnswer(); + } + else if ( state === states.NEXT ) { + this.initNext(); + } + } ); + + // Disable 'Check' button when all coefficients are zero. + const coefficientsSumObserver = coefficientsSum => { + this.checkButton.enabled = ( coefficientsSum > 0 ); + }; + model.currentEquationProperty.link( ( newEquation, oldEquation ) => { + if ( oldEquation ) { oldEquation.coefficientsSumProperty.unlink( coefficientsSumObserver ); } + if ( newEquation ) { newEquation.coefficientsSumProperty.link( coefficientsSumObserver ); } + } ); + + this.mutate( options ); + } - // Call an initializer to set up the game for the state. - model.stateProperty.link( state => { - const states = model.states; - if ( state === states.CHECK ) { - this.initCheck(); - } - else if ( state === states.TRY_AGAIN ) { - this.initTryAgain(); - } - else if ( state === states.SHOW_ANSWER ) { - this.initShowAnswer(); - } - else if ( state === states.NEXT ) { - this.initNext(); - } - } ); - - // Disable 'Check' button when all coefficients are zero. - const coefficientsSumObserver = coefficientsSum => { - this.checkButton.enabled = ( coefficientsSum > 0 ); - }; - model.currentEquationProperty.link( ( newEquation, oldEquation ) => { - if ( oldEquation ) { oldEquation.coefficientsSumProperty.unlink( coefficientsSumObserver ); } - if ( newEquation ) { newEquation.coefficientsSumProperty.link( coefficientsSumObserver ); } - } ); + // No dispose needed, instances of this type persist for lifetime of the sim. - this.mutate( options ); - } + // @private + initCheck() { + this.equationNode.setEnabled( true ); + this.checkButton.visible = true; + this.nextButton.visible = false; + this.setFeedbackDialogVisible( false ); + this.setBalancedHighlightEnabled( false ); + } - // No dispose needed, instances of this type persist for lifetime of the sim. + // @private + initTryAgain() { + this.equationNode.setEnabled( false ); + this.checkButton.visible = this.nextButton.visible = false; + this.setFeedbackDialogVisible( true ); + this.setBalancedHighlightEnabled( false ); + } - // @private - initCheck() { - this.equationNode.setEnabled( true ); - this.checkButton.visible = true; - this.nextButton.visible = false; - this.setFeedbackDialogVisible( false ); - this.setBalancedHighlightEnabled( false ); - } + // @private + initShowAnswer() { + this.equationNode.setEnabled( false ); + this.checkButton.visible = this.nextButton.visible = false; + this.setFeedbackDialogVisible( true ); + this.setBalancedHighlightEnabled( false ); + } - // @private - initTryAgain() { - this.equationNode.setEnabled( false ); - this.checkButton.visible = this.nextButton.visible = false; - this.setFeedbackDialogVisible( true ); - this.setBalancedHighlightEnabled( false ); - } + // @private + initNext() { - // @private - initShowAnswer() { - this.equationNode.setEnabled( false ); - this.checkButton.visible = this.nextButton.visible = false; - this.setFeedbackDialogVisible( true ); - this.setBalancedHighlightEnabled( false ); - } + this.equationNode.setEnabled( false ); + this.checkButton.visible = false; - // @private - initNext() { + const currentEquation = this.model.currentEquationProperty.get(); + this.nextButton.visible = !currentEquation.balancedAndSimplified; // 'Next' button is in the game feedback dialog + this.setFeedbackDialogVisible( currentEquation.balancedAndSimplified ); + this.setBalancedHighlightEnabled( true ); + currentEquation.balance(); // show the correct answer (do this last!) + } - this.equationNode.setEnabled( false ); - this.checkButton.visible = false; + /* + * Turns on/off the highlighting feature that indicates whether the equation is balanced. + * We need to be able to control this so that a balanced equation doesn't highlight + * until after the user has completed a challenge. + * @private + */ + setBalancedHighlightEnabled( enabled ) { + this.equationNode.setBalancedHighlightEnabled( enabled ); + this.boxesNode.setBalancedHighlightEnabled( enabled ); + } - const currentEquation = this.model.currentEquationProperty.get(); - this.nextButton.visible = !currentEquation.balancedAndSimplified; // 'Next' button is in the game feedback dialog - this.setFeedbackDialogVisible( currentEquation.balancedAndSimplified ); - this.setBalancedHighlightEnabled( true ); - currentEquation.balance(); // show the correct answer (do this last!) + /** + * Plays a sound corresponding to whether the user's guess is correct or incorrect. + * @private + */ + playGuessAudio() { + if ( this.model.currentEquationProperty.get().balancedAndSimplified ) { + this.audioPlayer.correctAnswer(); } - - /* - * Turns on/off the highlighting feature that indicates whether the equation is balanced. - * We need to be able to control this so that a balanced equation doesn't highlight - * until after the user has completed a challenge. - * @private - */ - setBalancedHighlightEnabled( enabled ) { - this.equationNode.setBalancedHighlightEnabled( enabled ); - this.boxesNode.setBalancedHighlightEnabled( enabled ); + else { + this.audioPlayer.wrongAnswer(); } + } - /** - * Plays a sound corresponding to whether the user's guess is correct or incorrect. - * @private - */ - playGuessAudio() { - if ( this.model.currentEquationProperty.get().balancedAndSimplified ) { - this.audioPlayer.correctAnswer(); - } - else { - this.audioPlayer.wrongAnswer(); - } + /** + * Controls the visibility of the game feedback dialog. + * This tells the user whether their guess is correct or not. + * @param visible + * @private + */ + setFeedbackDialogVisible( visible ) { + if ( this.feedbackDialog ) { + this.removeChild( this.feedbackDialog ); + this.feedbackDialog = null; } - - /** - * Controls the visibility of the game feedback dialog. - * This tells the user whether their guess is correct or not. - * @param visible - * @private - */ - setFeedbackDialogVisible( visible ) { - if ( this.feedbackDialog ) { - this.removeChild( this.feedbackDialog ); - this.feedbackDialog = null; - } - if ( visible ) { - this.feedbackDialog = new GameFeedbackDialog( this.model, this.aligner, - { centerX: this.layoutBounds.centerX, top: this.boxesNode.top + 10 } ); - this.addChild( this.feedbackDialog ); // visible and in front - } + if ( visible ) { + this.feedbackDialog = new GameFeedbackDialog( this.model, this.aligner, + { centerX: this.layoutBounds.centerX, top: this.boxesNode.top + 10 } ); + this.addChild( this.feedbackDialog ); // visible and in front } } +} - return balancingChemicalEquations.register( 'GamePlayNode', GamePlayNode ); -} ); +balancingChemicalEquations.register( 'GamePlayNode', GamePlayNode ); +export default GamePlayNode; \ No newline at end of file diff --git a/js/game/view/GameScreenView.js b/js/game/view/GameScreenView.js index 74027eb5..e87ad566 100644 --- a/js/game/view/GameScreenView.js +++ b/js/game/view/GameScreenView.js @@ -5,150 +5,147 @@ * * @author Vasily Shakhov (MLearner) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const BCEQueryParameters = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEQueryParameters' ); - const BCERewardNode = require( 'BALANCING_CHEMICAL_EQUATIONS/game/view/BCERewardNode' ); - const GameAudioPlayer = require( 'VEGAS/GameAudioPlayer' ); - const GamePlayNode = require( 'BALANCING_CHEMICAL_EQUATIONS/game/view/GamePlayNode' ); - const GameViewProperties = require( 'BALANCING_CHEMICAL_EQUATIONS/game/view/GameViewProperties' ); - const LevelCompletedNode = require( 'VEGAS/LevelCompletedNode' ); - const LevelSelectionNode = require( 'BALANCING_CHEMICAL_EQUATIONS/game/view/LevelSelectionNode' ); - const Node = require( 'SCENERY/nodes/Node' ); - const ScreenView = require( 'JOIST/ScreenView' ); - - class GameScreenView extends ScreenView { - - /** - * @param {GameModel} model - */ - constructor( model ) { - - super( BCEConstants.SCREEN_VIEW_OPTIONS ); - - // @public view-specific Properties - this.viewProperties = new GameViewProperties(); - - this.model = model; // @public - this.audioPlayer = new GameAudioPlayer(); // @public - - // @private Add a root node where all of the game-related nodes will live. - this.rootNode = new Node(); - this.addChild( this.rootNode ); - - // @private level-selection interface - this.levelSelectionNode = new LevelSelectionNode( this.model, this.viewProperties, this.layoutBounds, - this.initStartGame.bind( this ), { visible: false } ); - this.rootNode.addChild( this.levelSelectionNode ); - - // @private game-play interface, created on demand - this.gamePlayNode = null; - - // Call an initializer to set up the game for the state. - model.stateProperty.link( state => { - if ( state === model.states.LEVEL_SELECTION ) { - this.initLevelSelection(); - } - else if ( state === model.states.LEVEL_COMPLETED ) { - this.initLevelCompleted(); - } - } ); - } - // No dispose needed, instances of this type persist for lifetime of the sim. - - // @public - step( dt ) { - if ( this.rewardNode ) { - this.rewardNode.step( dt ); +import ScreenView from '../../../../joist/js/ScreenView.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import GameAudioPlayer from '../../../../vegas/js/GameAudioPlayer.js'; +import LevelCompletedNode from '../../../../vegas/js/LevelCompletedNode.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../../common/BCEConstants.js'; +import BCEQueryParameters from '../../common/BCEQueryParameters.js'; +import BCERewardNode from './BCERewardNode.js'; +import GamePlayNode from './GamePlayNode.js'; +import GameViewProperties from './GameViewProperties.js'; +import LevelSelectionNode from './LevelSelectionNode.js'; + +class GameScreenView extends ScreenView { + + /** + * @param {GameModel} model + */ + constructor( model ) { + + super( BCEConstants.SCREEN_VIEW_OPTIONS ); + + // @public view-specific Properties + this.viewProperties = new GameViewProperties(); + + this.model = model; // @public + this.audioPlayer = new GameAudioPlayer(); // @public + + // @private Add a root node where all of the game-related nodes will live. + this.rootNode = new Node(); + this.addChild( this.rootNode ); + + // @private level-selection interface + this.levelSelectionNode = new LevelSelectionNode( this.model, this.viewProperties, this.layoutBounds, + this.initStartGame.bind( this ), { visible: false } ); + this.rootNode.addChild( this.levelSelectionNode ); + + // @private game-play interface, created on demand + this.gamePlayNode = null; + + // Call an initializer to set up the game for the state. + model.stateProperty.link( state => { + if ( state === model.states.LEVEL_SELECTION ) { + this.initLevelSelection(); } - } + else if ( state === model.states.LEVEL_COMPLETED ) { + this.initLevelCompleted(); + } + } ); + } + + // No dispose needed, instances of this type persist for lifetime of the sim. - // @private - initLevelSelection() { - if ( this.gamePlayNode ) { this.gamePlayNode.visible = false; } - this.levelSelectionNode.visible = true; + // @public + step( dt ) { + if ( this.rewardNode ) { + this.rewardNode.step( dt ); } + } - /** - * Performs initialization to start a game for a specified level. - * @param {number} level - * @private - */ - initStartGame( level ) { - this.model.levelProperty.set( level ); - if ( !this.gamePlayNode ) { - this.gamePlayNode = new GamePlayNode( this.model, this.viewProperties, this.audioPlayer, - this.layoutBounds, this.visibleBoundsProperty ); - this.rootNode.addChild( this.gamePlayNode ); - } - this.viewProperties.reactantsBoxExpandedProperty.reset(); - this.viewProperties.productsBoxExpandedProperty.reset(); - this.levelSelectionNode.visible = false; - this.gamePlayNode.visible = true; - this.model.startGame(); + // @private + initLevelSelection() { + if ( this.gamePlayNode ) { this.gamePlayNode.visible = false; } + this.levelSelectionNode.visible = true; + } + + /** + * Performs initialization to start a game for a specified level. + * @param {number} level + * @private + */ + initStartGame( level ) { + this.model.levelProperty.set( level ); + if ( !this.gamePlayNode ) { + this.gamePlayNode = new GamePlayNode( this.model, this.viewProperties, this.audioPlayer, + this.layoutBounds, this.visibleBoundsProperty ); + this.rootNode.addChild( this.gamePlayNode ); } + this.viewProperties.reactantsBoxExpandedProperty.reset(); + this.viewProperties.productsBoxExpandedProperty.reset(); + this.levelSelectionNode.visible = false; + this.gamePlayNode.visible = true; + this.model.startGame(); + } - // @private - initLevelCompleted() { + // @private + initLevelCompleted() { - const self = this; - const level = this.model.levelProperty.get(); + const self = this; + const level = this.model.levelProperty.get(); - this.levelSelectionNode.visible = this.gamePlayNode.visible = false; + this.levelSelectionNode.visible = this.gamePlayNode.visible = false; - // game reward, shown for perfect score (or with 'reward' query parameter) - if ( this.model.isPerfectScore() || BCEQueryParameters.showReward ) { - this.rewardNode = new BCERewardNode( level ); - this.rootNode.addChild( this.rewardNode ); - } + // game reward, shown for perfect score (or with 'reward' query parameter) + if ( this.model.isPerfectScore() || BCEQueryParameters.showReward ) { + this.rewardNode = new BCERewardNode( level ); + this.rootNode.addChild( this.rewardNode ); + } - // bestTime on level, must be null to not show in popup - const bestTimeOnThisLevel = this.model.bestTimeProperties[ level ].get() === 0 ? null : this.model.bestTimeProperties[ level ].get(); - - // Add the dialog node that indicates that the level has been completed. - const numberOfEquations = this.model.getNumberOfEquations( level ); - var levelCompletedNode = new LevelCompletedNode( level + 1, this.model.pointsProperty.get(), this.model.getPerfectScore( level ), - numberOfEquations, this.viewProperties.timerEnabledProperty.value, - this.model.timer.elapsedTimeProperty.value, bestTimeOnThisLevel, this.model.isNewBestTime, - - // function called when 'Continue' button is pressed - function() { - // remove the reward, if we have one - if ( self.rewardNode ) { - self.rootNode.removeChild( self.rewardNode ); - self.rewardNode.dispose(); - self.rewardNode = null; - } - // remove the level-completed dialog - self.rootNode.removeChild( levelCompletedNode ); - // go back to the level-selection screen - self.model.stateProperty.set( self.model.states.LEVEL_SELECTION ); - }, - { - // LevelCompletedNode options - starDiameter: Math.min( 60, 300 / numberOfEquations ), - centerX: this.layoutBounds.centerX, - centerY: this.layoutBounds.centerY, - levelVisible: false, - maxWidth: 0.85 * this.layoutBounds.width // constrain width for i18n + // bestTime on level, must be null to not show in popup + const bestTimeOnThisLevel = this.model.bestTimeProperties[ level ].get() === 0 ? null : this.model.bestTimeProperties[ level ].get(); + + // Add the dialog node that indicates that the level has been completed. + const numberOfEquations = this.model.getNumberOfEquations( level ); + var levelCompletedNode = new LevelCompletedNode( level + 1, this.model.pointsProperty.get(), this.model.getPerfectScore( level ), + numberOfEquations, this.viewProperties.timerEnabledProperty.value, + this.model.timer.elapsedTimeProperty.value, bestTimeOnThisLevel, this.model.isNewBestTime, + + // function called when 'Continue' button is pressed + function() { + // remove the reward, if we have one + if ( self.rewardNode ) { + self.rootNode.removeChild( self.rewardNode ); + self.rewardNode.dispose(); + self.rewardNode = null; } - ); - this.rootNode.addChild( levelCompletedNode ); - - // Play the appropriate audio feedback. - if ( this.model.isPerfectScore() ) { - this.audioPlayer.gameOverPerfectScore(); - } - else { - this.audioPlayer.gameOverImperfectScore(); + // remove the level-completed dialog + self.rootNode.removeChild( levelCompletedNode ); + // go back to the level-selection screen + self.model.stateProperty.set( self.model.states.LEVEL_SELECTION ); + }, + { + // LevelCompletedNode options + starDiameter: Math.min( 60, 300 / numberOfEquations ), + centerX: this.layoutBounds.centerX, + centerY: this.layoutBounds.centerY, + levelVisible: false, + maxWidth: 0.85 * this.layoutBounds.width // constrain width for i18n } + ); + this.rootNode.addChild( levelCompletedNode ); + + // Play the appropriate audio feedback. + if ( this.model.isPerfectScore() ) { + this.audioPlayer.gameOverPerfectScore(); + } + else { + this.audioPlayer.gameOverImperfectScore(); } } +} - return balancingChemicalEquations.register( 'GameScreenView', GameScreenView ); -} ); +balancingChemicalEquations.register( 'GameScreenView', GameScreenView ); +export default GameScreenView; \ No newline at end of file diff --git a/js/game/view/GameViewProperties.js b/js/game/view/GameViewProperties.js index 1baba197..8bd7e32f 100644 --- a/js/game/view/GameViewProperties.js +++ b/js/game/view/GameViewProperties.js @@ -5,30 +5,27 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BooleanProperty = require( 'AXON/BooleanProperty' ); +import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; - class GameViewProperties { +class GameViewProperties { - constructor() { - - // @public - this.timerEnabledProperty = new BooleanProperty( false ); - this.reactantsBoxExpandedProperty = new BooleanProperty( true ); - this.productsBoxExpandedProperty = new BooleanProperty( true ); - } + constructor() { // @public - reset() { - this.timerEnabledProperty.reset(); - this.reactantsBoxExpandedProperty.reset(); - this.productsBoxExpandedProperty.reset(); - } + this.timerEnabledProperty = new BooleanProperty( false ); + this.reactantsBoxExpandedProperty = new BooleanProperty( true ); + this.productsBoxExpandedProperty = new BooleanProperty( true ); + } + + // @public + reset() { + this.timerEnabledProperty.reset(); + this.reactantsBoxExpandedProperty.reset(); + this.productsBoxExpandedProperty.reset(); } +} - return balancingChemicalEquations.register( 'GameViewProperties', GameViewProperties ); -} ); +balancingChemicalEquations.register( 'GameViewProperties', GameViewProperties ); +export default GameViewProperties; \ No newline at end of file diff --git a/js/game/view/LevelSelectionNode.js b/js/game/view/LevelSelectionNode.js index 9294fd59..36252f8a 100644 --- a/js/game/view/LevelSelectionNode.js +++ b/js/game/view/LevelSelectionNode.js @@ -5,135 +5,131 @@ * * @author Vasily Shakhov (mlearner.com) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const HBox = require( 'SCENERY/nodes/HBox' ); - const LevelSelectionButton = require( 'VEGAS/LevelSelectionButton' ); - const merge = require( 'PHET_CORE/merge' ); - const MoleculeFactory = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/MoleculeFactory' ); - const Node = require( 'SCENERY/nodes/Node' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const ResetAllButton = require( 'SCENERY_PHET/buttons/ResetAllButton' ); - const ScoreDisplayStars = require( 'VEGAS/ScoreDisplayStars' ); - const StringUtils = require( 'PHETCOMMON/util/StringUtils' ); - const Text = require( 'SCENERY/nodes/Text' ); - const TimerToggleButton = require( 'SCENERY_PHET/buttons/TimerToggleButton' ); - const VBox = require( 'SCENERY/nodes/VBox' ); - - // images, ordered by level - const levelImagesConstructors = [ - MoleculeFactory.HCl().nodeConstructor, - MoleculeFactory.H2O().nodeConstructor, - MoleculeFactory.NH3().nodeConstructor - ]; - - // strings - const chooseYourLevelString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/chooseYourLevel' ); - const pattern0LevelString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/pattern_0level' ); - - // constants - const BUTTON_MARGIN = 20; - - class LevelSelectionNode extends Node { - - /** - * @param {GameModel} model - * @param {GameViewProperties} viewProperties - * @param {Bounds2} layoutBounds - * @param {function(number:level)} startGame - * @param {Object} [options] - */ - constructor( model, viewProperties, layoutBounds, startGame, options ) { - - super(); - - // buttons - const buttons = []; - for ( let level = model.LEVELS_RANGE.min; level <= model.LEVELS_RANGE.max; level++ ) { - buttons.push( createLevelSelectionButton( level, model, viewProperties.timerEnabledProperty, startGame ) ); - } - const buttonsParent = new HBox( { - children: buttons, - spacing: 50, - resize: false, - center: layoutBounds.center - } ); - this.addChild( buttonsParent ); - - // title - const title = new Text( chooseYourLevelString, { - font: new PhetFont( 36 ), - centerX: layoutBounds.centerX, - centerY: buttonsParent.top / 2, - maxWidth: 0.85 * layoutBounds.width // constrain width for i18n - } ); - this.addChild( title ); - - // timer control, lower left - const toggleOptions = { stroke: 'black', cornerRadius: 10 }; - const timerToggleButton = new TimerToggleButton( viewProperties.timerEnabledProperty, merge( toggleOptions, { - x: BUTTON_MARGIN, - bottom: layoutBounds.bottom - BUTTON_MARGIN - } ) ); - this.addChild( timerToggleButton ); - - // Reset All button, lower right - const resetButton = new ResetAllButton( { - listener: function() { - model.reset(); - viewProperties.reset(); - }, - right: layoutBounds.right - BUTTON_MARGIN, - bottom: layoutBounds.bottom - BUTTON_MARGIN - } ); - this.addChild( resetButton ); - - this.mutate( options ); - } - // No dispose needed, instances of this type persist for lifetime of the sim. - } +import merge from '../../../../phet-core/js/merge.js'; +import StringUtils from '../../../../phetcommon/js/util/StringUtils.js'; +import ResetAllButton from '../../../../scenery-phet/js/buttons/ResetAllButton.js'; +import TimerToggleButton from '../../../../scenery-phet/js/buttons/TimerToggleButton.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import HBox from '../../../../scenery/js/nodes/HBox.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import VBox from '../../../../scenery/js/nodes/VBox.js'; +import LevelSelectionButton from '../../../../vegas/js/LevelSelectionButton.js'; +import ScoreDisplayStars from '../../../../vegas/js/ScoreDisplayStars.js'; +import balancingChemicalEquationsStrings from '../../balancing-chemical-equations-strings.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../../common/BCEConstants.js'; +import MoleculeFactory from '../../common/model/MoleculeFactory.js'; + +// images, ordered by level +const levelImagesConstructors = [ + MoleculeFactory.HCl().nodeConstructor, + MoleculeFactory.H2O().nodeConstructor, + MoleculeFactory.NH3().nodeConstructor +]; + +const chooseYourLevelString = balancingChemicalEquationsStrings.chooseYourLevel; +const pattern0LevelString = balancingChemicalEquationsStrings.pattern_0level; + +// constants +const BUTTON_MARGIN = 20; + +class LevelSelectionNode extends Node { /** - * Creates a level selection button - * - * @param {number} level * @param {GameModel} model - * @param {Property.} bestTimeVisibleProperty + * @param {GameViewProperties} viewProperties + * @param {Bounds2} layoutBounds * @param {function(number:level)} startGame - * @returns {LevelSelectionButton} + * @param {Object} [options] */ - function createLevelSelectionButton( level, model, bestTimeVisibleProperty, startGame ) { + constructor( model, viewProperties, layoutBounds, startGame, options ) { - // 'Level N' centered above icon - const image = new levelImagesConstructors[ level ]( merge( { scale: 2 }, BCEConstants.ATOM_OPTIONS ) ); - const label = new Text( StringUtils.format( pattern0LevelString, level + 1 ), { - font: new PhetFont( { size: 14, weight: 'bold' } ), - maxWidth: image.width + super(); + + // buttons + const buttons = []; + for ( let level = model.LEVELS_RANGE.min; level <= model.LEVELS_RANGE.max; level++ ) { + buttons.push( createLevelSelectionButton( level, model, viewProperties.timerEnabledProperty, startGame ) ); + } + const buttonsParent = new HBox( { + children: buttons, + spacing: 50, + resize: false, + center: layoutBounds.center } ); - const icon = new VBox( { children: [ label, image ], spacing: 10 } ); - - return new LevelSelectionButton( icon, model.bestScoreProperties[ level ], { - baseColor: '#d9ebff', - buttonWidth: 155, - buttonHeight: 155, - bestTimeProperty: model.bestTimeProperties[ level ], - bestTimeVisibleProperty: bestTimeVisibleProperty, - scoreDisplayConstructor: ScoreDisplayStars, - scoreDisplayOptions: { - numberOfStars: model.getNumberOfEquations( level ), - perfectScore: model.getPerfectScore( level ) - }, + this.addChild( buttonsParent ); + + // title + const title = new Text( chooseYourLevelString, { + font: new PhetFont( 36 ), + centerX: layoutBounds.centerX, + centerY: buttonsParent.top / 2, + maxWidth: 0.85 * layoutBounds.width // constrain width for i18n + } ); + this.addChild( title ); + + // timer control, lower left + const toggleOptions = { stroke: 'black', cornerRadius: 10 }; + const timerToggleButton = new TimerToggleButton( viewProperties.timerEnabledProperty, merge( toggleOptions, { + x: BUTTON_MARGIN, + bottom: layoutBounds.bottom - BUTTON_MARGIN + } ) ); + this.addChild( timerToggleButton ); + + // Reset All button, lower right + const resetButton = new ResetAllButton( { listener: function() { - startGame( level ); - } + model.reset(); + viewProperties.reset(); + }, + right: layoutBounds.right - BUTTON_MARGIN, + bottom: layoutBounds.bottom - BUTTON_MARGIN } ); + this.addChild( resetButton ); + + this.mutate( options ); } - return balancingChemicalEquations.register( 'LevelSelectionNode', LevelSelectionNode ); -} ); + // No dispose needed, instances of this type persist for lifetime of the sim. +} + +/** + * Creates a level selection button + * + * @param {number} level + * @param {GameModel} model + * @param {Property.} bestTimeVisibleProperty + * @param {function(number:level)} startGame + * @returns {LevelSelectionButton} + */ +function createLevelSelectionButton( level, model, bestTimeVisibleProperty, startGame ) { + + // 'Level N' centered above icon + const image = new levelImagesConstructors[ level ]( merge( { scale: 2 }, BCEConstants.ATOM_OPTIONS ) ); + const label = new Text( StringUtils.format( pattern0LevelString, level + 1 ), { + font: new PhetFont( { size: 14, weight: 'bold' } ), + maxWidth: image.width + } ); + const icon = new VBox( { children: [ label, image ], spacing: 10 } ); + + return new LevelSelectionButton( icon, model.bestScoreProperties[ level ], { + baseColor: '#d9ebff', + buttonWidth: 155, + buttonHeight: 155, + bestTimeProperty: model.bestTimeProperties[ level ], + bestTimeVisibleProperty: bestTimeVisibleProperty, + scoreDisplayConstructor: ScoreDisplayStars, + scoreDisplayOptions: { + numberOfStars: model.getNumberOfEquations( level ), + perfectScore: model.getPerfectScore( level ) + }, + listener: function() { + startGame( level ); + } + } ); +} +balancingChemicalEquations.register( 'LevelSelectionNode', LevelSelectionNode ); +export default LevelSelectionNode; \ No newline at end of file diff --git a/js/introduction/IntroductionScreen.js b/js/introduction/IntroductionScreen.js index e1f48880..e2b074ca 100644 --- a/js/introduction/IntroductionScreen.js +++ b/js/introduction/IntroductionScreen.js @@ -6,107 +6,104 @@ * @author Vasily Shakhov (mlearner.com) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - - // modules - const ArrowNode = require( 'SCENERY_PHET/ArrowNode' ); - const AtomNode = require( 'NITROGLYCERIN/nodes/AtomNode' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const Element = require( 'NITROGLYCERIN/Element' ); - const IntroductionModel = require( 'BALANCING_CHEMICAL_EQUATIONS/introduction/model/IntroductionModel' ); - const IntroductionScreenView = require( 'BALANCING_CHEMICAL_EQUATIONS/introduction/view/IntroductionScreenView' ); - const LinearGradient = require( 'SCENERY/util/LinearGradient' ); - const Node = require( 'SCENERY/nodes/Node' ); - const Path = require( 'SCENERY/nodes/Path' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Property = require( 'AXON/Property' ); - const Rectangle = require( 'SCENERY/nodes/Rectangle' ); - const Screen = require( 'JOIST/Screen' ); - const Shape = require( 'KITE/Shape' ); - const Text = require( 'SCENERY/nodes/Text' ); - - // strings - const screenIntroductionString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/screen.introduction' ); - - class IntroductionScreen extends Screen { - - constructor() { - - const options = { - name: screenIntroductionString, - backgroundColorProperty: new Property( BCEConstants.INTRODUCTION_CANVAS_BACKGROUND ), - homeScreenIcon: createScreenIcon() - }; - - super( - () => new IntroductionModel(), - model => new IntroductionScreenView( model ), - options - ); - } - } - // creates the icon for this screen: an equation above a balance beam - function createScreenIcon() { - - // background rectangle - const width = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE.width; - const height = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE.height; - const background = new Rectangle( 0, 0, width, height, { fill: 'white' } ); - - // equation: X + Y -> XY - const textOptions = { font: new PhetFont( { size: 24, weight: 'bold' } ) }; - const xPlusYText = new Text( 'X + Y', textOptions ); - const xyText = new Text( 'XY', textOptions ); - const arrowLength = 25; - const arrowNode = new ArrowNode( 0, 0, arrowLength, 0, { stroke: null, headWidth: 15 } ); - const equationNode = new Node( { children: [ xPlusYText, arrowNode, xyText ] } ); - const equationXSpacing = 5; - arrowNode.left = xPlusYText.right + equationXSpacing; - arrowNode.centerY = xPlusYText.centerY; - xyText.left = arrowNode.right + equationXSpacing; - - // balance beam, in horizontal (balanced) orientation - const beamNode = new Rectangle( 0, 0, equationNode.width, 10, { fill: 'yellow', stroke: 'black', lineWidth: 0.25 } ); - beamNode.left = equationNode.left; - beamNode.top = equationNode.bottom + 25; - - // fulcrum, centered below balance beam - const fulcrumWidth = 0.25 * beamNode.width; - const fulcrumHeight = 20; - const fulcrumFill = new LinearGradient( 0, 0, 0, fulcrumHeight ).addColorStop( 0, 'white' ).addColorStop( 1, 'rgb(192, 192, 192)' ); - const fulcrumNode = new Path( new Shape().moveTo( 0, 0 ).lineTo( fulcrumWidth / 2, fulcrumHeight ).lineTo( -fulcrumWidth / 2, fulcrumHeight ).close(), - { fill: fulcrumFill, stroke: 'black', lineWidth: 0.25 } ); - fulcrumNode.centerX = beamNode.centerX; - fulcrumNode.top = beamNode.bottom; - - // atoms, left to right (this is brute force, but is unlikely to change) - const atomsXMargin = 10; - const atom1 = new AtomNode( Element.O ); - const atom2 = new AtomNode( Element.O ); - const atom3 = new AtomNode( Element.O ); - const atom4 = new AtomNode( Element.O ); - // all atoms are on top of the beam - atom1.bottom = beamNode.top; - atom2.bottom = beamNode.top; - atom3.bottom = beamNode.top; - atom4.bottom = beamNode.top; - // atoms on the left of the beam - atom1.left = beamNode.left + atomsXMargin; - atom2.left = atom1.right; - // atom on the right of the beam - atom4.right = beamNode.right - atomsXMargin; - atom3.right = atom4.left; - - // scale to fit, center in background - const contentNode = new Node( { children: [ equationNode, beamNode, fulcrumNode, atom1, atom2, atom3, atom4 ] } ); - contentNode.setScaleMagnitude( Math.min( 0.82 * background.width / contentNode.width, 0.82 * background.height / contentNode.height ) ); - contentNode.center = background.center; - - return new Node( { children: [ background, contentNode ] } ); +import Property from '../../../axon/js/Property.js'; +import Screen from '../../../joist/js/Screen.js'; +import Shape from '../../../kite/js/Shape.js'; +import Element from '../../../nitroglycerin/js/Element.js'; +import AtomNode from '../../../nitroglycerin/js/nodes/AtomNode.js'; +import ArrowNode from '../../../scenery-phet/js/ArrowNode.js'; +import PhetFont from '../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../scenery/js/nodes/Node.js'; +import Path from '../../../scenery/js/nodes/Path.js'; +import Rectangle from '../../../scenery/js/nodes/Rectangle.js'; +import Text from '../../../scenery/js/nodes/Text.js'; +import LinearGradient from '../../../scenery/js/util/LinearGradient.js'; +import balancingChemicalEquationsStrings from '../balancing-chemical-equations-strings.js'; +import balancingChemicalEquations from '../balancingChemicalEquations.js'; +import BCEConstants from '../common/BCEConstants.js'; +import IntroductionModel from './model/IntroductionModel.js'; +import IntroductionScreenView from './view/IntroductionScreenView.js'; + +const screenIntroductionString = balancingChemicalEquationsStrings.screen.introduction; + +class IntroductionScreen extends Screen { + + constructor() { + + const options = { + name: screenIntroductionString, + backgroundColorProperty: new Property( BCEConstants.INTRODUCTION_CANVAS_BACKGROUND ), + homeScreenIcon: createScreenIcon() + }; + + super( + () => new IntroductionModel(), + model => new IntroductionScreenView( model ), + options + ); } - - return balancingChemicalEquations.register( 'IntroductionScreen', IntroductionScreen ); -} ); \ No newline at end of file +} + +// creates the icon for this screen: an equation above a balance beam +function createScreenIcon() { + + // background rectangle + const width = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE.width; + const height = Screen.MINIMUM_HOME_SCREEN_ICON_SIZE.height; + const background = new Rectangle( 0, 0, width, height, { fill: 'white' } ); + + // equation: X + Y -> XY + const textOptions = { font: new PhetFont( { size: 24, weight: 'bold' } ) }; + const xPlusYText = new Text( 'X + Y', textOptions ); + const xyText = new Text( 'XY', textOptions ); + const arrowLength = 25; + const arrowNode = new ArrowNode( 0, 0, arrowLength, 0, { stroke: null, headWidth: 15 } ); + const equationNode = new Node( { children: [ xPlusYText, arrowNode, xyText ] } ); + const equationXSpacing = 5; + arrowNode.left = xPlusYText.right + equationXSpacing; + arrowNode.centerY = xPlusYText.centerY; + xyText.left = arrowNode.right + equationXSpacing; + + // balance beam, in horizontal (balanced) orientation + const beamNode = new Rectangle( 0, 0, equationNode.width, 10, { fill: 'yellow', stroke: 'black', lineWidth: 0.25 } ); + beamNode.left = equationNode.left; + beamNode.top = equationNode.bottom + 25; + + // fulcrum, centered below balance beam + const fulcrumWidth = 0.25 * beamNode.width; + const fulcrumHeight = 20; + const fulcrumFill = new LinearGradient( 0, 0, 0, fulcrumHeight ).addColorStop( 0, 'white' ).addColorStop( 1, 'rgb(192, 192, 192)' ); + const fulcrumNode = new Path( new Shape().moveTo( 0, 0 ).lineTo( fulcrumWidth / 2, fulcrumHeight ).lineTo( -fulcrumWidth / 2, fulcrumHeight ).close(), + { fill: fulcrumFill, stroke: 'black', lineWidth: 0.25 } ); + fulcrumNode.centerX = beamNode.centerX; + fulcrumNode.top = beamNode.bottom; + + // atoms, left to right (this is brute force, but is unlikely to change) + const atomsXMargin = 10; + const atom1 = new AtomNode( Element.O ); + const atom2 = new AtomNode( Element.O ); + const atom3 = new AtomNode( Element.O ); + const atom4 = new AtomNode( Element.O ); + // all atoms are on top of the beam + atom1.bottom = beamNode.top; + atom2.bottom = beamNode.top; + atom3.bottom = beamNode.top; + atom4.bottom = beamNode.top; + // atoms on the left of the beam + atom1.left = beamNode.left + atomsXMargin; + atom2.left = atom1.right; + // atom on the right of the beam + atom4.right = beamNode.right - atomsXMargin; + atom3.right = atom4.left; + + // scale to fit, center in background + const contentNode = new Node( { children: [ equationNode, beamNode, fulcrumNode, atom1, atom2, atom3, atom4 ] } ); + contentNode.setScaleMagnitude( Math.min( 0.82 * background.width / contentNode.width, 0.82 * background.height / contentNode.height ) ); + contentNode.center = background.center; + + return new Node( { children: [ background, contentNode ] } ); +} + +balancingChemicalEquations.register( 'IntroductionScreen', IntroductionScreen ); +export default IntroductionScreen; \ No newline at end of file diff --git a/js/introduction/model/IntroductionModel.js b/js/introduction/model/IntroductionModel.js index db36fe58..defb5a11 100644 --- a/js/introduction/model/IntroductionModel.js +++ b/js/introduction/model/IntroductionModel.js @@ -6,51 +6,48 @@ * * @author Vasily Shakhov (MLearner) */ -define( require => { - 'use strict'; - - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const DecompositionEquation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/DecompositionEquation' ); - const DisplacementEquation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/DisplacementEquation' ); - const Property = require( 'AXON/Property' ); - const Range = require( 'DOT/Range' ); - const SynthesisEquation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/SynthesisEquation' ); - - // strings - const combustMethaneString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/combustMethane' ); - const makeAmmoniaString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/makeAmmonia' ); - const separateWaterString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/separateWater' ); - - class IntroductionModel { - - constructor() { - - this.COEFFICENTS_RANGE = new Range( 0, 3 ); // @public (read-only) Range of possible equation coefficients - - /* - * @public - * Choices available in the 'Introduction' screen. - * The contract for a choice is: { equation: {Equation}, label: {string} } - */ - this.choices = [ - { equation: SynthesisEquation.create_N2_3H2_2NH3(), label: makeAmmoniaString }, - { equation: DecompositionEquation.create_2H2O_2H2_O2(), label: separateWaterString }, - { equation: DisplacementEquation.create_CH4_2O2_CO2_2H2O(), label: combustMethaneString } - ]; - - // @public the equation that is selected - this.equationProperty = new Property( this.choices[ 0 ].equation ); - } - - // @public - reset() { - this.equationProperty.reset(); - this.choices.forEach( function( choice ) { - choice.equation.reset(); - } ); - } + +import Property from '../../../../axon/js/Property.js'; +import Range from '../../../../dot/js/Range.js'; +import balancingChemicalEquationsStrings from '../../balancing-chemical-equations-strings.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import DecompositionEquation from '../../common/model/DecompositionEquation.js'; +import DisplacementEquation from '../../common/model/DisplacementEquation.js'; +import SynthesisEquation from '../../common/model/SynthesisEquation.js'; + +const combustMethaneString = balancingChemicalEquationsStrings.combustMethane; +const makeAmmoniaString = balancingChemicalEquationsStrings.makeAmmonia; +const separateWaterString = balancingChemicalEquationsStrings.separateWater; + +class IntroductionModel { + + constructor() { + + this.COEFFICENTS_RANGE = new Range( 0, 3 ); // @public (read-only) Range of possible equation coefficients + + /* + * @public + * Choices available in the 'Introduction' screen. + * The contract for a choice is: { equation: {Equation}, label: {string} } + */ + this.choices = [ + { equation: SynthesisEquation.create_N2_3H2_2NH3(), label: makeAmmoniaString }, + { equation: DecompositionEquation.create_2H2O_2H2_O2(), label: separateWaterString }, + { equation: DisplacementEquation.create_CH4_2O2_CO2_2H2O(), label: combustMethaneString } + ]; + + // @public the equation that is selected + this.equationProperty = new Property( this.choices[ 0 ].equation ); + } + + // @public + reset() { + this.equationProperty.reset(); + this.choices.forEach( function( choice ) { + choice.equation.reset(); + } ); } +} - return balancingChemicalEquations.register( 'IntroductionModel', IntroductionModel ); -} ); \ No newline at end of file +balancingChemicalEquations.register( 'IntroductionModel', IntroductionModel ); +export default IntroductionModel; \ No newline at end of file diff --git a/js/introduction/view/EquationChoiceNode.js b/js/introduction/view/EquationChoiceNode.js index 72866171..e4e27b6e 100644 --- a/js/introduction/view/EquationChoiceNode.js +++ b/js/introduction/view/EquationChoiceNode.js @@ -5,68 +5,65 @@ * * @author Vasily Shakhov (MLearner) */ -define( require => { - 'use strict'; - // modules - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const HorizontalAquaRadioButtonGroup = require( 'SUN/HorizontalAquaRadioButtonGroup' ); - const Node = require( 'SCENERY/nodes/Node' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Rectangle = require( 'SCENERY/nodes/Rectangle' ); - const Text = require( 'SCENERY/nodes/Text' ); +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Rectangle from '../../../../scenery/js/nodes/Rectangle.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import HorizontalAquaRadioButtonGroup from '../../../../sun/js/HorizontalAquaRadioButtonGroup.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; - // constants - const BAR_HEIGHT = 50; //height of control node - const TEXT_OPTIONS = { font: new PhetFont( 16 ), fill: 'white' }; +// constants +const BAR_HEIGHT = 50; //height of control node +const TEXT_OPTIONS = { font: new PhetFont( 16 ), fill: 'white' }; - class EquationChoiceNode extends Node { +class EquationChoiceNode extends Node { - /** - * @param {number} screenWidth - * @param {Property.} equationProperty - * @param {{ equation: {Equation}, label: {string} }[]} choices - * @param {Object} [options] - */ - constructor( screenWidth, equationProperty, choices, options ) { + /** + * @param {number} screenWidth + * @param {Property.} equationProperty + * @param {{ equation: {Equation}, label: {string} }[]} choices + * @param {Object} [options] + */ + constructor( screenWidth, equationProperty, choices, options ) { - super(); + super(); - // background, extra wide so that it will appear to fill the entire screen for all but extreme window sizes - this.addChild( new Rectangle( 0, 0, 4 * screenWidth, BAR_HEIGHT, { - fill: '#3376c4', - centerX: screenWidth / 2 - } ) ); + // background, extra wide so that it will appear to fill the entire screen for all but extreme window sizes + this.addChild( new Rectangle( 0, 0, 4 * screenWidth, BAR_HEIGHT, { + fill: '#3376c4', + centerX: screenWidth / 2 + } ) ); - // radio button descriptions, one button for each equation - const radioButtonItems = []; - choices.forEach( function( choice ) { - radioButtonItems.push( { - node: new Text( choice.label, TEXT_OPTIONS ), - value: choice.equation - } ); + // radio button descriptions, one button for each equation + const radioButtonItems = []; + choices.forEach( function( choice ) { + radioButtonItems.push( { + node: new Text( choice.label, TEXT_OPTIONS ), + value: choice.equation } ); + } ); - // radio button group, horizontally layout - const radioButtonGroup = new HorizontalAquaRadioButtonGroup( equationProperty, radioButtonItems, { - radioButtonOptions: { radius: 8 }, - touchAreaYDilation: 15, - spacing: 30, - left: 50, - centerY: BAR_HEIGHT / 2, - maxWidth: 0.8 * screenWidth - } ); - this.addChild( radioButtonGroup ); - - this.disposeEquationChoiceNode = function() { - radioButtonGroup.dispose(); - }; + // radio button group, horizontally layout + const radioButtonGroup = new HorizontalAquaRadioButtonGroup( equationProperty, radioButtonItems, { + radioButtonOptions: { radius: 8 }, + touchAreaYDilation: 15, + spacing: 30, + left: 50, + centerY: BAR_HEIGHT / 2, + maxWidth: 0.8 * screenWidth + } ); + this.addChild( radioButtonGroup ); - this.mutate( options ); - } + this.disposeEquationChoiceNode = function() { + radioButtonGroup.dispose(); + }; - // No dispose needed, instances of this type persist for lifetime of the sim. + this.mutate( options ); } - return balancingChemicalEquations.register( 'EquationChoiceNode', EquationChoiceNode ); -} ); + // No dispose needed, instances of this type persist for lifetime of the sim. +} + +balancingChemicalEquations.register( 'EquationChoiceNode', EquationChoiceNode ); +export default EquationChoiceNode; \ No newline at end of file diff --git a/js/introduction/view/IntroductionScreenView.js b/js/introduction/view/IntroductionScreenView.js index c6ab02a2..8394129a 100644 --- a/js/introduction/view/IntroductionScreenView.js +++ b/js/introduction/view/IntroductionScreenView.js @@ -5,131 +5,128 @@ * * @author Vasily Shakhov (MLearner) */ -define( require => { - 'use strict'; - - // modules - const BalancedRepresentation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/BalancedRepresentation' ); - const BalanceScalesNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BalanceScalesNode' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BarChartsNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BarChartsNode' ); - const BCEConstants = require( 'BALANCING_CHEMICAL_EQUATIONS/common/BCEConstants' ); - const BoxesNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/BoxesNode' ); - const Dimension2 = require( 'DOT/Dimension2' ); - const EquationChoiceNode = require( 'BALANCING_CHEMICAL_EQUATIONS/introduction/view/EquationChoiceNode' ); - const EquationNode = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/EquationNode' ); - const FaceNode = require( 'SCENERY_PHET/FaceNode' ); - const HorizontalAligner = require( 'BALANCING_CHEMICAL_EQUATIONS/common/view/HorizontalAligner' ); - const IntroductionViewProperties = require( 'BALANCING_CHEMICAL_EQUATIONS/introduction/view/IntroductionViewProperties' ); - const Node = require( 'SCENERY/nodes/Node' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const ResetAllButton = require( 'SCENERY_PHET/buttons/ResetAllButton' ); - const ScreenView = require( 'JOIST/ScreenView' ); - const Text = require( 'SCENERY/nodes/Text' ); - const ToolsComboBox = require( 'BALANCING_CHEMICAL_EQUATIONS/introduction/view/ToolsComboBox' ); - - // constants - const BOX_SIZE = new Dimension2( 285, 145 ); - const BOX_X_SPACING = 110; // horizontal spacing between boxes - - class IntroductionScreenView extends ScreenView { - - /** - * @param {IntroductionModel} model - */ - constructor( model ) { - - super( BCEConstants.SCREEN_VIEW_OPTIONS ); - - // view-specific Properties - const viewProperties = new IntroductionViewProperties(); - - // aligner for equation - const aligner = new HorizontalAligner( this.layoutBounds.width, BOX_SIZE.width, BOX_X_SPACING ); - - // boxes that show molecules corresponding to the equation coefficients - const boxesNode = new BoxesNode( model.equationProperty, model.COEFFICENTS_RANGE, aligner, - BOX_SIZE, BCEConstants.BOX_COLOR, viewProperties.reactantsBoxExpandedProperty, viewProperties.productsBoxExpandedProperty, - { top: 180 } ); - this.addChild( boxesNode ); - - // 'Tools' combo box, at upper-right - const comboBoxParent = new Node(); - this.addChild( new ToolsComboBox( viewProperties.balancedRepresentationProperty, comboBoxParent, - { right: this.layoutBounds.right - 45, top: this.layoutBounds.top + 15 } ) ); - - // smiley face, top center, shown when equation is balanced - const faceNode = new FaceNode( 70, { centerX: this.layoutBounds.centerX, top: 15 } ); - this.addChild( faceNode ); - const updateFace = () => { - faceNode.visible = model.equationProperty.get().balancedProperty.get(); - }; - model.equationProperty.link( ( newEquation, oldEquation ) => { - if ( oldEquation ) { oldEquation.balancedProperty.unlink( updateFace ); } - newEquation.balancedProperty.link( updateFace ); - } ); - - // interactive equation - this.addChild( new EquationNode( model.equationProperty, model.COEFFICENTS_RANGE, aligner, { top: boxesNode.bottom + 20 } ) ); - - // control for choosing an equation - const equationChoiceNode = new EquationChoiceNode( this.layoutBounds.width, model.equationProperty, model.choices, { bottom: this.layoutBounds.bottom - 10 } ); - this.addChild( equationChoiceNode ); - - // Reset All button - this.addChild( new ResetAllButton( { - listener: () => { - model.reset(); - viewProperties.reset(); - }, - right: this.layoutBounds.right - 20, - centerY: equationChoiceNode.centerY, - scale: 0.8 - } ) ); - - // Show the selected 'balanced' representation, create nodes on demand. - const balancedParent = new Node(); // to maintain rendering order for combo box - this.addChild( balancedParent ); - let barChartsNode; - let balanceScalesNode; - viewProperties.balancedRepresentationProperty.link( balancedRepresentation => { - - // bar chart - if ( !barChartsNode && balancedRepresentation === BalancedRepresentation.BAR_CHARTS ) { - barChartsNode = new BarChartsNode( model.equationProperty, aligner, { - bottom: boxesNode.top - 10 - } ); - balancedParent.addChild( barChartsNode ); - } - if ( barChartsNode ) { - barChartsNode.visible = ( balancedRepresentation === BalancedRepresentation.BAR_CHARTS ); - } - - // balance scales - if ( !balanceScalesNode && balancedRepresentation === BalancedRepresentation.BALANCE_SCALES ) { - balanceScalesNode = new BalanceScalesNode( model.equationProperty, aligner, - { bottom: boxesNode.top - 10, dualFulcrumSpacing: 325 } ); // use special spacing for 2 fulcrums, see issue #91 - balancedParent.addChild( balanceScalesNode ); - } - if ( balanceScalesNode ) { - balanceScalesNode.visible = ( balancedRepresentation === BalancedRepresentation.BALANCE_SCALES ); - } - } ); - // add this last, so that combo box list is on top of everything else - this.addChild( comboBoxParent ); - - // show the answer when running in dev mode, bottom center - if ( phet.chipper.queryParameters.showAnswers ) { - const answerNode = new Text( '', { font: new PhetFont( 12 ), bottom: equationChoiceNode.top - 5 } ); - this.addChild( answerNode ); - model.equationProperty.link( equation => { - answerNode.text = equation.getCoefficientsString(); - answerNode.centerX = this.layoutBounds.centerX; +import Dimension2 from '../../../../dot/js/Dimension2.js'; +import ScreenView from '../../../../joist/js/ScreenView.js'; +import ResetAllButton from '../../../../scenery-phet/js/buttons/ResetAllButton.js'; +import FaceNode from '../../../../scenery-phet/js/FaceNode.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BCEConstants from '../../common/BCEConstants.js'; +import BalancedRepresentation from '../../common/model/BalancedRepresentation.js'; +import BalanceScalesNode from '../../common/view/BalanceScalesNode.js'; +import BarChartsNode from '../../common/view/BarChartsNode.js'; +import BoxesNode from '../../common/view/BoxesNode.js'; +import EquationNode from '../../common/view/EquationNode.js'; +import HorizontalAligner from '../../common/view/HorizontalAligner.js'; +import EquationChoiceNode from './EquationChoiceNode.js'; +import IntroductionViewProperties from './IntroductionViewProperties.js'; +import ToolsComboBox from './ToolsComboBox.js'; + +// constants +const BOX_SIZE = new Dimension2( 285, 145 ); +const BOX_X_SPACING = 110; // horizontal spacing between boxes + +class IntroductionScreenView extends ScreenView { + + /** + * @param {IntroductionModel} model + */ + constructor( model ) { + + super( BCEConstants.SCREEN_VIEW_OPTIONS ); + + // view-specific Properties + const viewProperties = new IntroductionViewProperties(); + + // aligner for equation + const aligner = new HorizontalAligner( this.layoutBounds.width, BOX_SIZE.width, BOX_X_SPACING ); + + // boxes that show molecules corresponding to the equation coefficients + const boxesNode = new BoxesNode( model.equationProperty, model.COEFFICENTS_RANGE, aligner, + BOX_SIZE, BCEConstants.BOX_COLOR, viewProperties.reactantsBoxExpandedProperty, viewProperties.productsBoxExpandedProperty, + { top: 180 } ); + this.addChild( boxesNode ); + + // 'Tools' combo box, at upper-right + const comboBoxParent = new Node(); + this.addChild( new ToolsComboBox( viewProperties.balancedRepresentationProperty, comboBoxParent, + { right: this.layoutBounds.right - 45, top: this.layoutBounds.top + 15 } ) ); + + // smiley face, top center, shown when equation is balanced + const faceNode = new FaceNode( 70, { centerX: this.layoutBounds.centerX, top: 15 } ); + this.addChild( faceNode ); + const updateFace = () => { + faceNode.visible = model.equationProperty.get().balancedProperty.get(); + }; + model.equationProperty.link( ( newEquation, oldEquation ) => { + if ( oldEquation ) { oldEquation.balancedProperty.unlink( updateFace ); } + newEquation.balancedProperty.link( updateFace ); + } ); + + // interactive equation + this.addChild( new EquationNode( model.equationProperty, model.COEFFICENTS_RANGE, aligner, { top: boxesNode.bottom + 20 } ) ); + + // control for choosing an equation + const equationChoiceNode = new EquationChoiceNode( this.layoutBounds.width, model.equationProperty, model.choices, { bottom: this.layoutBounds.bottom - 10 } ); + this.addChild( equationChoiceNode ); + + // Reset All button + this.addChild( new ResetAllButton( { + listener: () => { + model.reset(); + viewProperties.reset(); + }, + right: this.layoutBounds.right - 20, + centerY: equationChoiceNode.centerY, + scale: 0.8 + } ) ); + + // Show the selected 'balanced' representation, create nodes on demand. + const balancedParent = new Node(); // to maintain rendering order for combo box + this.addChild( balancedParent ); + let barChartsNode; + let balanceScalesNode; + viewProperties.balancedRepresentationProperty.link( balancedRepresentation => { + + // bar chart + if ( !barChartsNode && balancedRepresentation === BalancedRepresentation.BAR_CHARTS ) { + barChartsNode = new BarChartsNode( model.equationProperty, aligner, { + bottom: boxesNode.top - 10 } ); + balancedParent.addChild( barChartsNode ); + } + if ( barChartsNode ) { + barChartsNode.visible = ( balancedRepresentation === BalancedRepresentation.BAR_CHARTS ); } + + // balance scales + if ( !balanceScalesNode && balancedRepresentation === BalancedRepresentation.BALANCE_SCALES ) { + balanceScalesNode = new BalanceScalesNode( model.equationProperty, aligner, + { bottom: boxesNode.top - 10, dualFulcrumSpacing: 325 } ); // use special spacing for 2 fulcrums, see issue #91 + balancedParent.addChild( balanceScalesNode ); + } + if ( balanceScalesNode ) { + balanceScalesNode.visible = ( balancedRepresentation === BalancedRepresentation.BALANCE_SCALES ); + } + } ); + + // add this last, so that combo box list is on top of everything else + this.addChild( comboBoxParent ); + + // show the answer when running in dev mode, bottom center + if ( phet.chipper.queryParameters.showAnswers ) { + const answerNode = new Text( '', { font: new PhetFont( 12 ), bottom: equationChoiceNode.top - 5 } ); + this.addChild( answerNode ); + model.equationProperty.link( equation => { + answerNode.text = equation.getCoefficientsString(); + answerNode.centerX = this.layoutBounds.centerX; + } ); } } +} - return balancingChemicalEquations.register( 'IntroductionScreenView', IntroductionScreenView ); -} ); +balancingChemicalEquations.register( 'IntroductionScreenView', IntroductionScreenView ); +export default IntroductionScreenView; \ No newline at end of file diff --git a/js/introduction/view/IntroductionViewProperties.js b/js/introduction/view/IntroductionViewProperties.js index 9aa61766..af1c6817 100644 --- a/js/introduction/view/IntroductionViewProperties.js +++ b/js/introduction/view/IntroductionViewProperties.js @@ -5,32 +5,29 @@ * * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const BalancedRepresentation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/BalancedRepresentation' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const BooleanProperty = require( 'AXON/BooleanProperty' ); - const StringProperty = require( 'AXON/StringProperty' ); +import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; +import StringProperty from '../../../../axon/js/StringProperty.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BalancedRepresentation from '../../common/model/BalancedRepresentation.js'; - class IntroductionViewProperties { +class IntroductionViewProperties { - constructor() { - - // @public - this.reactantsBoxExpandedProperty = new BooleanProperty( true ); - this.productsBoxExpandedProperty = new BooleanProperty( true ); - this.balancedRepresentationProperty = new StringProperty( BalancedRepresentation.NONE ); - } + constructor() { // @public - reset() { - this.reactantsBoxExpandedProperty.reset(); - this.productsBoxExpandedProperty.reset(); - this.balancedRepresentationProperty.reset(); - } + this.reactantsBoxExpandedProperty = new BooleanProperty( true ); + this.productsBoxExpandedProperty = new BooleanProperty( true ); + this.balancedRepresentationProperty = new StringProperty( BalancedRepresentation.NONE ); + } + + // @public + reset() { + this.reactantsBoxExpandedProperty.reset(); + this.productsBoxExpandedProperty.reset(); + this.balancedRepresentationProperty.reset(); } +} - return balancingChemicalEquations.register( 'IntroductionViewProperties', IntroductionViewProperties ); -} ); +balancingChemicalEquations.register( 'IntroductionViewProperties', IntroductionViewProperties ); +export default IntroductionViewProperties; \ No newline at end of file diff --git a/js/introduction/view/ToolsComboBox.js b/js/introduction/view/ToolsComboBox.js index 3767c2e2..632821f1 100644 --- a/js/introduction/view/ToolsComboBox.js +++ b/js/introduction/view/ToolsComboBox.js @@ -6,66 +6,61 @@ * @author Vasily Shakhov (MLearner) * @author Chris Malley (PixelZoom, Inc.) */ -define( require => { - 'use strict'; - // modules - const BalancedRepresentation = require( 'BALANCING_CHEMICAL_EQUATIONS/common/model/BalancedRepresentation' ); - const balancingChemicalEquations = require( 'BALANCING_CHEMICAL_EQUATIONS/balancingChemicalEquations' ); - const ComboBox = require( 'SUN/ComboBox' ); - const ComboBoxItem = require( 'SUN/ComboBoxItem' ); - const Image = require( 'SCENERY/nodes/Image' ); - const merge = require( 'PHET_CORE/merge' ); - const PhetFont = require( 'SCENERY_PHET/PhetFont' ); - const Text = require( 'SCENERY/nodes/Text' ); +import merge from '../../../../phet-core/js/merge.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Image from '../../../../scenery/js/nodes/Image.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import ComboBox from '../../../../sun/js/ComboBox.js'; +import ComboBoxItem from '../../../../sun/js/ComboBoxItem.js'; +import chartsImage from '../../../images/charts_png.js'; +import scalesImage from '../../../mipmaps/scales_png.js'; +import balancingChemicalEquationsStrings from '../../balancing-chemical-equations-strings.js'; +import balancingChemicalEquations from '../../balancingChemicalEquations.js'; +import BalancedRepresentation from '../../common/model/BalancedRepresentation.js'; - // images - const chartsImage = require( 'image!BALANCING_CHEMICAL_EQUATIONS/charts.png' ); - const scalesImage = require( 'mipmap!BALANCING_CHEMICAL_EQUATIONS/scales.png' ); +const noneString = balancingChemicalEquationsStrings.none; +const toolsString = balancingChemicalEquationsStrings.tools; - // strings - const noneString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/none' ); - const toolsString = require( 'string!BALANCING_CHEMICAL_EQUATIONS/tools' ); +// constants +const FONT = new PhetFont( 22 ); - // constants - const FONT = new PhetFont( 22 ); +class ToolsComboBox extends ComboBox { - class ToolsComboBox extends ComboBox { + /** + * @param {Property.} balanceRepresentationProperty + * @param {Node} parentNode node that will be used as the list's parent, use this to ensuring that the list is in front of everything else + * @param {Object} [options] + * @constructor + */ + constructor( balanceRepresentationProperty, parentNode, options ) { - /** - * @param {Property.} balanceRepresentationProperty - * @param {Node} parentNode node that will be used as the list's parent, use this to ensuring that the list is in front of everything else - * @param {Object} [options] - * @constructor - */ - constructor( balanceRepresentationProperty, parentNode, options ) { + options = merge( { + xMargin: 10, + yMargin: 5, + cornerRadius: 4, + maxWidth: 600 + }, options ); - options = merge( { - xMargin: 10, - yMargin: 5, - cornerRadius: 4, - maxWidth: 600 - }, options ); + // options that cannot be specified by client + options.labelNode = new Text( toolsString, { + font: FONT, + fontWeight: 'bold', + maxWidth: 100 + } ); - // options that cannot be specified by client - options.labelNode = new Text( toolsString, { - font: FONT, - fontWeight: 'bold', - maxWidth: 100 - } ); + const items = [ + // 'None' + new ComboBoxItem( new Text( noneString, { font: FONT, maxWidth: 100 } ), BalancedRepresentation.NONE ), + // scales + new ComboBoxItem( new Image( scalesImage, { scale: 0.1875 } ), BalancedRepresentation.BALANCE_SCALES ), + // bar charts + new ComboBoxItem( new Image( chartsImage, { scale: 0.375 } ), BalancedRepresentation.BAR_CHARTS ) + ]; - const items = [ - // 'None' - new ComboBoxItem( new Text( noneString, { font: FONT, maxWidth: 100 } ), BalancedRepresentation.NONE ), - // scales - new ComboBoxItem( new Image( scalesImage, { scale: 0.1875 } ), BalancedRepresentation.BALANCE_SCALES ), - // bar charts - new ComboBoxItem( new Image( chartsImage, { scale: 0.375 } ), BalancedRepresentation.BAR_CHARTS ) - ]; - - super( items, balanceRepresentationProperty, parentNode, options ); - } + super( items, balanceRepresentationProperty, parentNode, options ); } +} - return balancingChemicalEquations.register( 'ToolsComboBox', ToolsComboBox ); -} ); +balancingChemicalEquations.register( 'ToolsComboBox', ToolsComboBox ); +export default ToolsComboBox; \ No newline at end of file diff --git a/mipmaps/scales_png.js b/mipmaps/scales_png.js new file mode 100644 index 00000000..fab09d28 --- /dev/null +++ b/mipmaps/scales_png.js @@ -0,0 +1,38 @@ +/* eslint-disable */ +var mipmaps = [ { + "width": 300, + "height": 163, + "url": "data:image/png;base64,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" +}, { + "width": 150, + "height": 82, + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAABSCAYAAAC2eC1AAAAAAklEQVR4AewaftIAAA3XSURBVO3BfZCW5XXA4d8597Psy+4q7PrFKn5kl8J+gCHaJioSgqRGIKgxQVGMMTFmYhynneaPTDqZTNPOmCZNmqYdaxJjoBo6GZlYaawRrZqKIqjEXZYlSiICMaWiZq3CuOzz3Of0pfxBU2SX3fdj3zDPdZHL5XK5XC6Xy+VyuVwul8v9HhByI2kGvgVMB+o4aEhENrr7nwOD5A4j5IZTEJGeJZdePqNr5kwaGxppbGjAzXjwwZ/y8MNrHwfmkTtMQm44N8+5cO6MZVdfw763B5lYKHB8UxOF+npOPOlktv1y2/t37njpQ8Bacr9DyQ1n/oUXziWEwP9XX1/PaaedRtHJ5A6j5IYzoamxidzoKbkjaQYSRlZP7jBK7kj+va6ubj7D2L9/P0V3APeQ+x0J4+tzQBu16cyrr7mW4QwODjLvA/P5j5899ofAN6g9+4CvAoNUWcL4mdvU1PTta65Znjjg7riDuwOOu+MOjoM77uA4uOMOjuPu4OCAu+EOTpE77o47RY67IyKAIAKCgICIcICKAIII/0tEeODffkJDocBImhqbmDbtD941b978z4uAiCAiHCAIIhQJjuPuuDvujju4O+6GA24OOOYO7jjgZjjg7rg77o67gwhCkQgCCEVCkYAIwkG7du3kiXWPTwOWU2UJ4+fUjo7O5GNLr8TNMRw3w9xxd9wcc8fdcTfMHHfH3HF33Bxzw91xd8wcd8fccTfcHHPH3XF3RAQRQURQEUQEEUFEUBFEBBFBRBBV1q17HMcZmXPGGWfw0Y9+DBFBVREEUUFEUBEOcHfMDHPHzHA3zBwzw9xxM8wdM8PNMHfMDHPHzTB3zAx3R0Q4QEQQEUQoEkQEERARQNi4cT1PrHv8FMZBwvh56Nlnn/mvi/94wRRqljAy4dFHH+HRRx+hBu0F7mEcJIyfAQ7aCZxF7fnV+vVPtl911RkM59e/3kXR08D7qC2PAZ3A9xgHyvi5jIOOB+ZSe77e17d5B8OYNGkSmzf3bgRWUVsKwLs56ArGgTJ+Pgn0Ar3ALdSe7wG/ZGS3AX9PbVkGvAb0ANcxDpTx0wHcBzwMTCdXTsuArcB9QAfjQBkfc4GTgZXACqAd6CJXLt3AXcBKYCpwLlWmjI8bgD5gENgN9AOfJlcOlwEJcC8wCGwCbqHKlPExG1jLIZuA95Irh08CWzlkI9BNlSnV1wW0Ays45AfAbKCZXKk6gNUc8h2gA2ilipTquxnoA3ZzyCZgJ7CcXCnmAq3ASg7ZDjwP3EQVKdV3DvAch+sHPkKuFDcAPcAgv6sfmEcVKdXVCswEbuNwq4AucqWYDazlcHcCs4ACVaJU143Ai8BWDrcGyIAryI1FF9AOrOBw64DXgGVUiVJdC4Aejmwb8HFyY3Ez0Afs5p1tBa6kSpTqKQCzgds4stVAJ7mxeA/wHEf2Y6CLKlGq53pgN/AMR7YSmAqcS240WoFZwG0c2d1AAzCPKlCqZynwC4Y3CGwCPkNuND4JvAhsZXj9wM1UgVI9XcBKRrYROIfcaHwI6GFkDwPTqQKlOq4AMmANI/sO0AG0kjsaBWAWcCcjux2YAXRRYUp1XAf0c3S2A9uAG8kdjeuBPcA6RjYA9ACfpsKU6ugCfsTR6wMWkDsaS4HnOXo/B86jwpTKmwucCPyIo3cnMAsokBtJF7CCo/ePwNlAMxWUUHmfBXqBQY7eOmAAWArcTWW1AncAHcCZQML/8aUvfZER3AXcxSFvAjtE5AF3/yKVdQUHreHo9QMvAp8CvkmFJFReN3APo9cHXA3cTWXdvnDR4sVLllzKSSefgpkxlGYMpSlZlpGEQF2SUFdXR5Ik1CUJdXV11CUJQZWhoSH2Dw2xf/9+9g8NsXffvuNffnnX2X/zta+enabpA8A6Kuc6oJfRewH4MPBNKiShsrqBGcDtjN6Pgb+iMi4EFgETROT4jRue8hkdHbLn1VeJ0chiRpZFYowEVUIIJCEQQiBJEpIQCCGgqmRpSpplDKUpaZoSY6R/Sx9pmg4BlwOXcdB64F7Kqwu4ldG7E1hJBSVU1ueA54ABRu9u4NvAXGAd5VEAVjc2Nl5y8YcuSSZObMDcMHN+85v/ZEvfFtI0RVQQEQRBRBARREBFQQQVQQREBBFFAFHBzJk0aRJTpkzh+us/NcHM/yy6kQ4N8fTGDZ/fseOlXuAGYBOlmwucCPyI0VsLZMBSYDUVkFBZ5wLrGbte4BZgHeVx76LFSxZ+5IqP0nTcccQYidGIMRLNiDGiqgRVVJWgioZAUCWooiEQVAmqqCohBFSVoIqqEmMki5EYI1mWkcVIlmXEGLn44kt47LFH3r3qh3fdB7wX2E1pPgv0AoOMTT+wHFhNBSiV0wp0A99n7B4GplMen2pvn7bw6muWM2nSJKqtrq6OBR+8mA8vuXQq8LeUrht4mLG7D+igQpTKuQl4HtjK2K0A2oEuSrd86VXLaGhoYDydd/4cii6kNF3ADOB2xm4lMBX4IypAqZx5QD+l2Q1sAT5N6ZpPPOEExtvUqVMpmgoUGLubgeeAAcZuENgE3EwFKJVRAM4F/oHS/Rx4L6VrbznhBGpBe/s0ii5i7M4BNlC6jUA3FaBUxvXAy8AmSvcDYDbQzNgtOPPMs45vaWmhFnR3z6RoIWPTCswEvk/pvgXMBFopM6UylgKbKY9NwIvAcsbu8veddx61oqOzi6JOxuYmYBuwldLtBrYAN1FmSmV0AasonxeAjzB2XbNmnU2tmNjQQNEpjM08oI/y6QfmUWZK+S0F9gNrKJ9VQBdjN3XS5MnUitbWUymaBhQYnQJwLnAb5XMbMBsoUEZK+S0Hnqe81gAZcAWj19bU1DT9tNOmUismTpxId/fMAnARo3M98DLwDOXzDPAKsIwyUspvFrCK8tsGfJzRWzT7Pe9hQl0dtaR75iyKFjI6S4HNlN9WYBllpJTXXKAZWE35rQY6Gb0F559/AbWmra2Nok5GpwtYRfmtAjooo4TyaBORFe4+E6gHtlEZpwO/Au4H/pSj03rSSSdTa5qajqPoFEbWKiKr3X020AjcR+XsBX4GfAwYpAQJZSAid91ww41zzrvgAtyMLMaGLItkMZLFSMwiB4QQCEFJQiCEQAiBoIEkBEIIhBAIqmRZRpplZFlGmmWkWUqWZiAw+Pbb7d+/47t/8tJL2/cAtzKyztZTT6XWTGltpWgaUAAGObI7ll/78TmLFy+hMHEiWczIskiWZWRZJM0y3J0kBJIkIYRAEgJJCCQhEJKEJASSEAghkMVImqakacpQmpKmKWmW4e689dabjff/5F8XP7T2p98FPkEJEkp3bnNzy5wll16GBiXLMrIskmYZWRbJYkaWRQ5IQiCEQBICSQiEJCEJgSQEkhAISUJQJc0y0jQlTVOGsow0TUnTlANCUK68ahlf++tbFwG3MrwF06fPOP6ElhZiNGpJfX093d0zC/39Wy4CHuCdFYAPLFq0mOOOO440y6ikSZMmM2/efB5a+9OLKJFSurkXzJnDhAkTqJYpradSdAYju/y888+nVnXPmkXRQo5seltbe2NLSwvVcuZZZ9LY2DgVaKYESulObm5upppUlaPU1dnVRa1qb5tGUSc1prGxkaJmSqCUbkNvTw/V9Oab/03RPkY2taW5hVrV1NRE0Skc2d49e17h7bffplpef/119uzZ8yawnRIopXto8+be17Zte4Fq2dzbQ9FzDK+tqalp+umnn06tmtLaStE0oMA72753795nnnziCarl6Y0bEZFnKFFC6QZF5Pav/MWXv3jJwkVJ3YQJWDSiGWaGmWFmHKCqqAiqiqqiqqgqKoqqoKqoCNGMGI1okRgjFo1okQN27dzJhg3rdwKfZ3hNLS0nEEIgxkgtqq8vMLm5ufDGwMCpwHbe2V9+4xtfX7F+/ZMn1k2ox90wd9wMd8fcwR0RRVRQBFFBRRBRRAVBUBVEBHfHzDBzzAwzw9w4YN/evfT29rwMfIESJZSBu3/5t7/97UP/vOqHi4AJVNbLwD8BAwxv265dOwff2vtWoWFiA7Xotdde5Y2BgVeB7RzZ/cDZ69c/+UEqbwh4CBigRAnl8wTwBLVjEHh244YNF86ffxG1aHNvDyLS4+6MYDdwN79HlGPbV779d9/a19fXR63p69vMD+68Y8Ddv8IxSDi2tQF3ABe9f94HaGtrw8xxN8wdN8fcMHPcHTNDRFBRRAURQUUQVRQQVUQEFUFEURVEBBVBRDB3zAwzw8wwc6IbHg1zw8wRhV07dvLUU09SdD/wGWA3xxjh2FUQkV9cs/zaszo7uzAzzAxzx6JhbpgZ0QyLhrlh5ogIqoqKoCqoBlQEVUVECKqIKiqCqqKqqAgigrkTY8TMiDESzYgxYmbEGDF3REBEUYEXnn+eNWv+pd/dZ3KMSTh2TZ88efJZ1133CWKMxGhEi8RoxBiJZsQYidGIMRLNiDGiqgRVVJWgioZAUCWooiEQVAmqqCohBFSVoIqqEmMki5EYI1mWkcVIlmXEGMmyjGiGqiCiqAodnV08+OAD3YODg6cAr3AMSTh2bXvjjTd2rFy54qzu7pmYGWaGuWPRMDfMjGiGRcPcMHNEBFVFRVAVVAMqgqoiIgRVRBUVQVVRVVQEEcHciTFiZsQYiWbEGDEzYoyYOyIgoqhAb28P+/fv3wK8wjFGOLa1AV8A3kVt+iXwJWCAXC6Xy+VyuVwul8vlcrlcLpfL5XK5I/of9A5QbyHpbN8AAAAASUVORK5CYII=" +}, { + "width": 75, + "height": 41, + "url": "data:image/png;base64,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" +}, { + "width": 38, + "height": 21, + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAVCAYAAAAq05ytAAAAAklEQVR4AewaftIAAAL6SURBVO3BTWgcVQDA8f97b3bmzexukm0wpSS7ZIOgxKaeRPBgm9JDSCs9id9BW0QQPBlEVFBaomKpClqhptaP4K31qFBKm4pHQWxTrCCt0iQ92GbXZDe7ybydMaIw+diZRczN/n7c9i8pNscDbxwa+35oaPjFs2fPnAL+4D+ySJYHBmhh+8COfd3dPV2SkGJf33PXrl79jmQBcAYIiCFI0NOTP/TkUyMHQ1huGINpNDCNBsY0CIIApSRKKuZu3Wwb3L1niyDkwuT5W729xQUpBL4xGGPwfR+/YQiCECUF1WrVO/nJ+NPAN8SwSDA9fT14+62xE8DrJHt+cPeeYwLBxMTnI8DXJPsQyJNA0lpAa3UisyTLATeBbSSQxNsJLABLwH1sngPAeUCQQBJvEBgHvgSG2DxZ4ALwOzBMDEm8EJgHrgOKzXccuJ8YFs3dAxgiAZADSkTkgzt3jfUWi89ks21bL138kXTaY3T0pR9mZmZ/vXFj9tTk5LnDwDyRZ4Ep/maAgBgWze0HviIyCRwAjvKPRx974v3hvfteWPYNQkrSrovnuriOw2Kt1jtXKo3mC4WtE198NkKkGxgn4gM7gIusI2nOBn4i8i2QZZX+/v57bdsmjrIs8oXCXawVstZp4CGakGyUAwI2Clmlo6OjSAuZTCZP5GFglrV+BiyasFjn8Nib59ra2vsXa/VXXK3JpjMIAXOlspiZmd713rtHBoHtnudtowXH0XcAA8C1d44cPW5rnavXlz/OeB7ZTBrf9ynPz4eXpy4VPj154iCrSNZJWSlPa9e2nb9oR2vtOI52tNa2ZVkeK3K5Lfs7OzstWmhv77C06+4FlJAypaREKYlSEqUUUiqUVGKFZh3BRncDfTR3GfgNeOSDYx9N2LZOVRdrCCHwtMZ1Ndp2qNXrLFQqlMul5ddefflx4DRwJ5ChuSnAsIrFRleAKyRIp9NdlYVKxTdlsVirI4RAOw6u42DbNktLS1SqVXzfD1OpVJfv+6z4hdv+j/4Eup/mTnhWrBEAAAAASUVORK5CYII=" +}, { + "width": 19, + "height": 11, + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAALCAYAAACd1bY6AAAAAklEQVR4AewaftIAAAFbSURBVM3ByUoCAQCA4X9GnRknHXWqQ82hQ1BBUK8QGHhqgyCIprcICtroYDsF3aLNyGOXHiCCoBdopWNkRh3KTJ0mlwQL9dCtQ9/HX3JQzQd0AgZgAEZLa1vX9Ozc2eXl+fVbIqEBBmAAjUCcCk4q1NXV942YowOiKCYt2yaXy2OlUx5VVT29PX3DgYBuZTIZLPuD92RSj+ztrAAn/MIETKqFtrYjBaCZaiZgUkHkDwmUBQEBcAHvwOny6lrU/swOal6v5BId1kvi9WhifGwIGARugA7gGHikyElZA3BAiQmc6rreXRAcUkDTcMuykrsrBClxAxfAIxACohQ5+ba2vrFaEITdWn+A2MNDfGkxHFfdan3K+uCHJMm6rCjB+YWlTUFw7Ps1jftY7GpqcjxKkUBZO1BDSVJRlKbw/OJh2rJFn9eDLEk8PT/nZ2cm+7PZ7C3gpCQNxPnXvgC2f2zj0mDqeAAAAABJRU5ErkJggg==" +} ]; +mipmaps.forEach( function( mipmap ) { + mipmap.img = new Image(); + window.phetImages.push( mipmap.img ); // make sure it's loaded before the sim launches + mipmap.img.src = mipmap.url; // trigger the loading of the image for its level + mipmap.canvas = document.createElement( 'canvas' ); + mipmap.canvas.width = mipmap.width; + mipmap.canvas.height = mipmap.height; + var context = mipmap.canvas.getContext( '2d' ); + mipmap.updateCanvas = function() { + if ( mipmap.img.complete && ( typeof mipmap.img.naturalWidth === 'undefined' || mipmap.img.naturalWidth > 0 ) ) { + context.drawImage( mipmap.img, 0, 0 ); + delete mipmap.updateCanvas; + } + }; +} ); +export default mipmaps; \ No newline at end of file