type Behavior = ( ball : Ball , elapsed : number ) => void ; class Ball { public behaviors : Behavior [ ] ; public radius : number ; public x : number ; public y : number ; public rotate : number ; public offset : number ; public moveSpeed : number ; public rotateSpeed : number ; constructor ( x , y , radius ) { this . x = x ; this . y = y ; this . radius = radius ; this . rotate = 0 ; this . offset = 0 ; this . moveSpeed = 0 ; this . rotateSpeed = 0 ; this . behaviors = [ ] ; } public setMoveSpeed ( speed ) { this . moveSpeed = speed ; this . setRotateSpeed ( speed / this . radius ) ; } public setRotateSpeed ( speed ) { this . rotateSpeed = 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 , rotate , offset } = this ; ctx . save ( ) ; ctx . translate ( x + offset , y ) ; ctx . rotate ( rotate ) ; ctx . beginPath ( ) ; ctx . arc ( 0 , 0 , radius , 0 , Math . PI * 2 , false ) ; ctx . moveTo ( - radius , 0 ) ; ctx . lineTo ( radius , 0 ) ; ctx . moveTo ( 0 , - radius ) ; ctx . lineTo ( 0 , radius ) ; ctx . fill ( ) ; ctx . stroke ( ) ; ctx . restore ( ) ; } public reset ( ) { this . rotate = 0 ; this . offset = 0 ; } static rotate ( ball , elapsed ) { let angle = ball . rotateSpeed * elapsed ; angle = angle % ( Math . PI * 2 ) ; ball . rotate = angle ; } static move ( ball , elapsed ) { let { moveSpeed } = ball ; let distance = ball . moveSpeed * elapsed ; ball . offset = distance ; } } export default Ball ;