type Behavior = ( ball : Ball , elapsed : number ) => void ; interface BallOption { verticalHeight ? : number ; pixelPerMiter ? : number ; useGravity ? : boolean ; useRebound ? : boolean ; } const GRAVITY = 9.81 ; class Ball { public behaviors : Behavior [ ] ; public radius : number ; public x : number ; public y : number ; public offset : number ; public moveSpeed : number ; public currentSpeed : number ; public pixelPerMiter : number ; public useGravity : boolean ; public useRebound : boolean ; public verticalHeight : number ; public isStill : boolean ; constructor ( x , y , radius , { verticalHeight = 0 , pixelPerMiter = 1 , useGravity = false , useRebound = false } = { } ) { this . x = x ; this . y = y ; this . radius = radius ; this . offset = 0 ; this . moveSpeed = 0 ; this . currentSpeed = 0 ; this . behaviors = [ ] ; this . pixelPerMiter = pixelPerMiter ; this . useGravity = useGravity ; this . useRebound = useRebound ; this . verticalHeight = verticalHeight ; this . isStill = false ; } public setSpeed ( speed ) { this . moveSpeed = speed ; this . currentSpeed = speed ; } public addBehavior ( behavior ) { if ( Array . isArray ( behavior ) ) { this . behaviors = [ ... this . behaviors , ... behavior ] ; } else { this . behaviors = [ ... this . behaviors , behavior ] ; } } public update ( elapsed ) { for ( let behavior of this . behaviors ) { behavior . call ( null , this , elapsed ) ; } } public render ( ctx ) { let { x , y , radius , offset , pixelPerMiter } = this ; ctx . save ( ) ; ctx . translate ( x , y + offset * pixelPerMiter ) ; ctx . beginPath ( ) ; ctx . arc ( 0 , 0 , radius , 0 , Math . PI * 2 , false ) ; ctx . fill ( ) ; ctx . stroke ( ) ; ctx . restore ( ) ; } public reset ( ) { this . offset = 0 ; this . currentSpeed = this . moveSpeed ; } static move ( ball , elapsed ) { if ( ball . isStill ) { return ; } let { currentSpeed } = ball ; let t = elapsed / 1000 ; if ( ball . useGravity ) { ball . currentSpeed += GRAVITY * t ; } let distance = ball . currentSpeed * t ; if ( ball . offset + distance > ball . verticalHeight ) { if ( ball . useRebound ) { ball . offset = ball . verticalHeight ; ball . currentSpeed = - ball . currentSpeed * 0.6 ; if ( ( distance * ball . pixelPerMiter ) / t < 1 ) { ball . isStill = true ; ball . currentSpeed = 0 ; } } else { ball . isStill = true ; ball . currentSpeed = 0 ; ball . offset = ball . verticalHeight ; } } else { ball . offset += distance ; } } } export default Ball ;