Skip to content

Commit

Permalink
Create SoccerPlayerPhase and refactor kicking logic to use phase - see
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-blackman committed Jul 6, 2023
1 parent 8b58315 commit b544f10
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 28 deletions.
3 changes: 2 additions & 1 deletion js/common/model/CAVSoccerSceneModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ export default class CAVSoccerSceneModel<T extends CAVSoccerBall = CAVSoccerBall
public constructor( maxKicksProperty: TReadOnlyProperty<number>,
maxKicksChoices: number[],
kickDistanceStrategy: TKickDistanceStrategy,
hidePlayersWhenDoneKicking: boolean,
physicalRange: Range,
kickDistanceStrategyFromStateObject: ( string: string ) => TKickDistanceStrategy,
soccerBallFactory: ( isFirstSoccerBall: boolean, options: CAVSoccerBallOptions ) => T,
providedOptions: CAVSoccerSceneModelOptions ) {

const options = providedOptions;
super( maxKicksProperty, maxKicksChoices, kickDistanceStrategy, physicalRange,
super( maxKicksProperty, maxKicksChoices, kickDistanceStrategy, hidePlayersWhenDoneKicking, physicalRange,
kickDistanceStrategyFromStateObject, soccerBallFactory, options );

this.medianValueProperty = new Property<number | null>( null, {
Expand Down
1 change: 1 addition & 0 deletions js/mean-and-median/model/MeanAndMedianModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export default class MeanAndMedianModel extends CAVModel {
MAX_KICKS_PROPERTY,
CAVConstants.MAX_KICKS_VALUES,
new RandomSkewStrategy(),
true,
CAVConstants.PHYSICAL_RANGE,
kickDistanceStrategyFromStateObject,
CAVSoccerBall.createSoccerBall, {
Expand Down
1 change: 1 addition & 0 deletions js/median/model/MedianModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export default class MedianModel extends CAVModel {
maxKicksProperty,
maxKicksAllowed,
new RandomSkewStrategy(),
true,
CAVConstants.PHYSICAL_RANGE,
kickDistanceStrategyFromStateObject,
CAVSoccerBall.createSoccerBall, {
Expand Down
32 changes: 20 additions & 12 deletions js/soccer-common/model/SoccerPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,37 @@

import soccerCommon from '../soccerCommon.js';
import Pose from './Pose.js';
import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import EnumerationProperty from '../../../../axon/js/EnumerationProperty.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import Property from '../../../../axon/js/Property.js';
import NullableIO from '../../../../tandem/js/types/NullableIO.js';
import NumberIO from '../../../../tandem/js/types/NumberIO.js';
import { SoccerPlayerPhase } from './SoccerPlayerPhase.js';
import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';

export default class SoccerPlayer {
public readonly poseProperty;
public readonly soccerPlayerPhaseProperty: Property<SoccerPlayerPhase>;
public readonly poseProperty: TReadOnlyProperty<Pose>;
public readonly timestampWhenPoisedBeganProperty: Property<number | null>;

// Also used to determine the artwork for rendering the SoccerPlayerNode
// Used to determine the artwork for rendering the SoccerPlayerNode
public readonly initialPlaceInLine: number;

public readonly timestampWhenPoisedBeganProperty: Property<number | null>;
public readonly isActiveProperty: BooleanProperty;

public constructor( placeInLine: number, tandem: Tandem ) {
this.poseProperty = new EnumerationProperty( Pose.STANDING, {
tandem: tandem.createTandem( 'poseProperty' )
this.soccerPlayerPhaseProperty = new EnumerationProperty( placeInLine === 0 ? SoccerPlayerPhase.READY : SoccerPlayerPhase.INACTIVE, {
tandem: tandem.createTandem( 'soccerPlayerPhaseProperty' )
} );
this.isActiveProperty = new BooleanProperty( placeInLine === 0, {
tandem: tandem.createTandem( 'isActiveProperty' )
this.poseProperty = new DerivedProperty( [ this.soccerPlayerPhaseProperty ], soccerPlayerPhase => {
if ( soccerPlayerPhase === SoccerPlayerPhase.POISED ) {
return Pose.POISED_TO_KICK;
}
else if ( soccerPlayerPhase === SoccerPlayerPhase.KICKING ) {
return Pose.KICKING;
}
else {
return Pose.STANDING;
}
} );
this.timestampWhenPoisedBeganProperty = new Property<number | null>( null, {
tandem: tandem.createTandem( 'timestampWhenPoisedBeganProperty' ),
Expand All @@ -40,9 +49,8 @@ export default class SoccerPlayer {
}

public reset(): void {
this.poseProperty.reset();
this.soccerPlayerPhaseProperty.reset();
this.timestampWhenPoisedBeganProperty.reset();
this.isActiveProperty.reset();
}
}

Expand Down
21 changes: 21 additions & 0 deletions js/soccer-common/model/SoccerPlayerPhase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2023, University of Colorado Boulder

import Enumeration from '../../../../phet-core/js/Enumeration.js';
import EnumerationValue from '../../../../phet-core/js/EnumerationValue.js';
import soccerCommon from '../soccerCommon.js';

/**
* SoccerPlayerPhase is used to identify what part of the kicking phase a SoccerPlayer is currently in
*
* @author Matthew Blackman (PhET Interactive Simulations)
*/

export class SoccerPlayerPhase extends EnumerationValue {
public static readonly INACTIVE = new SoccerPlayerPhase();
public static readonly READY = new SoccerPlayerPhase();
public static readonly POISED = new SoccerPlayerPhase();
public static readonly KICKING = new SoccerPlayerPhase();
private static readonly enumeration = new Enumeration( SoccerPlayerPhase );
}

soccerCommon.register( 'SoccerPlayerPhase', SoccerPlayerPhase );
20 changes: 10 additions & 10 deletions js/soccer-common/model/SoccerSceneModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ import SoccerPlayer from './SoccerPlayer.js';
import dotRandom from '../../../../dot/js/dotRandom.js';
import Animation from '../../../../twixt/js/Animation.js';
import Easing from '../../../../twixt/js/Easing.js';
import Pose from './Pose.js';
import { SoccerBallPhase } from './SoccerBallPhase.js';
import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import PhetioObject, { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js';
import Multilink from '../../../../axon/js/Multilink.js';
import { TKickDistanceStrategy } from './TKickDistanceStrategy.js';
import IOType from '../../../../tandem/js/types/IOType.js';
import VoidIO from '../../../../tandem/js/types/VoidIO.js';
Expand All @@ -45,6 +43,8 @@ import SoccerCommonQueryParameters from '../SoccerCommonQueryParameters.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import ArrayIO from '../../../../tandem/js/types/ArrayIO.js';
import KickDistanceStrategy from './KickDistanceStrategy.js';
import { SoccerPlayerPhase } from './SoccerPlayerPhase.js';
import Multilink from '../../../../axon/js/Multilink.js';

const kickSound = new SoundClip( basicKick_mp3, { initialOutputLevel: 0.2 } );
soundManager.addSoundGenerator( kickSound );
Expand Down Expand Up @@ -89,7 +89,7 @@ export default class SoccerSceneModel<T extends SoccerBall = SoccerBall> extends

private readonly timeWhenLastBallWasKickedProperty: NumberProperty;

// Starting at 0, iterate through the index of the kickers. This updates the SoccerPlayer.isActiveProperty to show the current kicker
// Starting at 0, iterate through the index of the kickers. This updates the SoccerPlayer.soccerPlayerPhaseProperty to show the current kicker
private readonly activeKickerIndexProperty: NumberProperty;

// Called when the location of a ball changed within a stack, so the pointer areas can be updated
Expand All @@ -110,6 +110,7 @@ export default class SoccerSceneModel<T extends SoccerBall = SoccerBall> extends
public readonly maxKicksProperty: TReadOnlyProperty<number>,
maxKicksChoices: number[],
initialKickDistanceStrategy: TKickDistanceStrategy,
hidePlayersWhenDoneKicking: boolean,
public readonly physicalRange: Range,
kickDistanceStrategyFromStateObject: ( string: string ) => TKickDistanceStrategy,
createSoccerBall: ( isFirstSoccerBall: boolean, options: { tandem: Tandem } ) => T,
Expand Down Expand Up @@ -263,7 +264,7 @@ export default class SoccerSceneModel<T extends SoccerBall = SoccerBall> extends

Multilink.multilink( [ this.activeKickerIndexProperty, this.maxKicksProperty ], ( activeKickerIndex, maxKicks ) => {
this.soccerPlayers.forEach( ( soccerPlayer, index ) => {
soccerPlayer.isActiveProperty.value = index === activeKickerIndex && index < maxKicks;
soccerPlayer.soccerPlayerPhaseProperty.value = index === activeKickerIndex && ( index < maxKicks || !hidePlayersWhenDoneKicking ) ? SoccerPlayerPhase.READY : SoccerPlayerPhase.INACTIVE;
} );
} );

Expand Down Expand Up @@ -433,14 +434,14 @@ export default class SoccerSceneModel<T extends SoccerBall = SoccerBall> extends

this.advanceLine();

if ( frontPlayer.poseProperty.value === Pose.STANDING ) {
frontPlayer.poseProperty.value = Pose.POISED_TO_KICK;
if ( frontPlayer.soccerPlayerPhaseProperty.value === SoccerPlayerPhase.READY ) {
frontPlayer.soccerPlayerPhaseProperty.value = SoccerPlayerPhase.POISED;
frontPlayer.timestampWhenPoisedBeganProperty.value = this.timeProperty.value;
}
}

// How long has the front player been poised?
if ( frontPlayer.poseProperty.value === Pose.POISED_TO_KICK ) {
if ( frontPlayer.soccerPlayerPhaseProperty.value === SoccerPlayerPhase.POISED ) {
assert && assert( typeof frontPlayer.timestampWhenPoisedBeganProperty.value === 'number', 'timestampWhenPoisedBegan should be a number' );
const elapsedTime = this.timeProperty.value - frontPlayer.timestampWhenPoisedBeganProperty.value!;
if ( elapsedTime > 0.075 ) {
Expand Down Expand Up @@ -488,8 +489,7 @@ export default class SoccerSceneModel<T extends SoccerBall = SoccerBall> extends

// Allow kicking another ball while one is already in the air.
// if the previous ball was still in the air, we need to move the line forward so the next player can kick
const kickers = this.soccerPlayers.filter( soccerPlayer => soccerPlayer.isActiveProperty.value &&
soccerPlayer.poseProperty.value === Pose.KICKING );
const kickers = this.soccerPlayers.filter( soccerPlayer => soccerPlayer.soccerPlayerPhaseProperty.value === SoccerPlayerPhase.KICKING );
if ( kickers.length > 0 ) {
let nextIndex = this.activeKickerIndexProperty.value + 1;
if ( nextIndex > this.maxKicksProperty.value ) {
Expand Down Expand Up @@ -570,7 +570,7 @@ export default class SoccerSceneModel<T extends SoccerBall = SoccerBall> extends
* Select a target location for the nextBallToKick, set its velocity and mark it for animation.
*/
private kickBall( soccerPlayer: SoccerPlayer, soccerBall: T, playAudio: boolean ): void {
soccerPlayer.poseProperty.value = Pose.KICKING;
soccerPlayer.soccerPlayerPhaseProperty.value = SoccerPlayerPhase.KICKING;

const x1 = SoccerCommonQueryParameters.sameSpot ? 7 :
this.kickDistanceStrategy.currentStrategy.getNextKickDistance( this.soccerBalls.indexOf( soccerBall ) );
Expand Down
10 changes: 5 additions & 5 deletions js/soccer-common/view/SoccerPlayerNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import optionize, { EmptySelfOptions } from '../../../../phet-core/js/optionize.
import Vector2 from '../../../../dot/js/Vector2.js';
import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js';
import Pose from '../model/Pose.js';
import Multilink from '../../../../axon/js/Multilink.js';
import { SoccerPlayerPhase } from '../model/SoccerPlayerPhase.js';

type SelfOptions = EmptySelfOptions;
type SoccerPlayerNodeOptions = SelfOptions & NodeOptions;
Expand Down Expand Up @@ -61,17 +61,17 @@ export default class SoccerPlayerNode extends Node {
} ) );
}

soccerPlayer.soccerPlayerPhaseProperty.link( phase => {
this.visible = phase !== SoccerPlayerPhase.INACTIVE;
} );

soccerPlayer.poseProperty.link( pose => {
standingNode.visible = pose === Pose.STANDING;
poisedToKickNode.visible = pose === Pose.POISED_TO_KICK;
kickingNode.visible = pose === Pose.KICKING;
this.centerBottom = modelViewTransform.modelToViewPosition( new Vector2( 0, 0 ) ).plusXY( -28, 8.5 );
} );

Multilink.multilink( [ soccerPlayer.isActiveProperty ], isActive => {
this.visible = isActive;
} );

const options = optionize<SoccerPlayerNodeOptions, SelfOptions, NodeOptions>()( {
excludeInvisibleChildrenFromBounds: false,
phetioVisiblePropertyInstrumented: false
Expand Down
1 change: 1 addition & 0 deletions js/variability/model/VariabilitySceneModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default class VariabilitySceneModel extends CAVSoccerSceneModel<Variabili
maxKicksProperty,
CAVConstants.MAX_KICKS_VALUES,
kickDistanceStrategy,
false,
CAVConstants.PHYSICAL_RANGE,
kickDistanceStrategyFromStateObject,
VariabilitySoccerBall.createSoccerBall,
Expand Down

0 comments on commit b544f10

Please sign in to comment.