001         package com.croftsoft.apps.fraction;
002         
003         import java.applet.*;
004         import java.awt.*;
005         import java.awt.event.*;
006         import java.awt.geom.*;
007         import java.net.URL;
008         import java.util.*;
009         import javax.swing.*;
010         import javax.swing.event.*;
011    
012         import com.croftsoft.core.CroftSoftConstants;
013         import com.croftsoft.core.animation.AnimatedComponent;
014         import com.croftsoft.core.animation.ComponentAnimator;
015         import com.croftsoft.core.animation.factory.DefaultAnimationFactory;
016         import com.croftsoft.core.animation.Sprite;
017         import com.croftsoft.core.animation.animator.TileAnimator;
018         import com.croftsoft.core.animation.clock.SystemClock;
019         import com.croftsoft.core.animation.collector.*;
020         import com.croftsoft.core.animation.sprite.IconSprite;
021         import com.croftsoft.core.animation.sprite.TextSprite;
022         import com.croftsoft.core.animation.updater.FrameRateUpdater;
023         import com.croftsoft.core.awt.font.FontLib;
024         import com.croftsoft.core.gui.FrameLib;
025         import com.croftsoft.core.lang.ClassLib;
026         import com.croftsoft.core.lang.lifecycle.Lifecycle;
027         import com.croftsoft.core.math.MathConstants;
028    
029         /*********************************************************************
030         * Main Fraction class.
031         *
032         * @version
033         *   2003-09-29
034         * @since
035         *   2002-04-28
036         * @author
037         *   <a href="https://www.croftsoft.com/">David Wallace Croft</a>
038         *********************************************************************/
039    
040         public final class  Fraction
041           extends JApplet
042           implements ComponentAnimator, Lifecycle
043         //////////////////////////////////////////////////////////////////////
044         //////////////////////////////////////////////////////////////////////
045         {
046           
047         //////////////////////////////////////////////////////////////////////
048         // Applet constants
049         //////////////////////////////////////////////////////////////////////
050    
051         private static final String  VERSION
052           = "2003-09-29";
053    
054         private static final String  TITLE
055           = "CroftSoft Fraction Action";
056    
057         private static final String  INFO
058           = "\n" + TITLE + "\n"
059           + "Version " + VERSION + "\n"
060           + CroftSoftConstants.COPYRIGHT + "\n"
061           + CroftSoftConstants.DEFAULT_LICENSE + "\n"
062           + CroftSoftConstants.HOME_PAGE + "\n";
063    
064         //////////////////////////////////////////////////////////////////////
065         // Frame constants
066         //////////////////////////////////////////////////////////////////////
067    
068         private static final String  FRAME_TITLE
069           = TITLE;
070    
071         private static final String  FRAME_ICON_FILENAME
072           = "/images/croftsoft.png";
073           
074         private static final Dimension  FRAME_SIZE
075           = null;
076    
077         private static final String  SHUTDOWN_CONFIRMATION_PROMPT
078           = "Close " + TITLE + "?";
079    
080         //////////////////////////////////////////////////////////////////////
081         // animation constants
082         //////////////////////////////////////////////////////////////////////
083    
084         private static final String  INIT_ANSWER_TEXT = "Fraction Action";
085    
086         /** frames per second */
087         private static final double  FRAME_RATE = 30.0;
088    
089         private static final String  MEDIA_DIR = "media/fraction/";
090    
091         private static final String  BONGO_AUDIO_FILENAME
092           = MEDIA_DIR + "bongo.wav";
093    
094         private static final String  POP_AUDIO_FILENAME
095           = MEDIA_DIR + "pop.wav";
096    
097         private static final Color   BACKGROUND_COLOR
098           = Color.BLACK;
099    
100         private static final Color   FLOOR_COLOR
101           = Color.GREEN;
102    
103         private static final Color   MOVEMENT_LINE_COLOR
104           = Color.RED;
105    
106         private static final Color   QUESTION_COLOR
107           = Color.GREEN;
108    
109         private static final Color   ANSWER_COLOR
110           = Color.BLUE;
111    
112         private static final Color   SCORE_COLOR
113           = Color.RED;
114    
115         private static final int     FLOORS = 12;
116    
117         private static final String  FONT_NAME = "Arioso";
118    
119         private static final int     FONT_STYLE = Font.BOLD;
120    
121         private static final String  FONT_NAME_SCORE = "Georgia";
122    
123         //////////////////////////////////////////////////////////////////////
124         // instance variables
125         //////////////////////////////////////////////////////////////////////
126    
127         private AnimatedComponent   animatedComponent;
128    
129         private TextSprite          questionTextSprite;
130    
131         private TextSprite          answerTextSprite;
132    
133         private TextSprite          scoreTextSprite;
134    
135         private FractionHeroSprite  fractionHeroSprite;
136    
137         private Rectangle           heroBounds;
138    
139         private FractionDoor [ ]    fractionDoors;
140    
141         private int                 floorHeight;
142    
143         private Rectangle           bounds;
144    
145         private Rectangle           paintBounds0;
146    
147         private Rectangle           paintBounds1;
148    
149         private FractionQuestion    fractionQuestion;
150    
151         private AudioClip           bongoAudioClip;
152    
153         private AudioClip           popAudioClip;
154    
155         private int                 score;
156    
157         /** pixels per frame */
158         private int                 spriteVelocity;
159    
160         private boolean             boundsInitialized;
161    
162         //////////////////////////////////////////////////////////////////////
163         //////////////////////////////////////////////////////////////////////
164    
165         public static void  main ( String [ ]  args )
166         //////////////////////////////////////////////////////////////////////
167         {
168           JFrame  jFrame = new JFrame ( FRAME_TITLE );
169    
170           try
171           {
172             jFrame.setIconImage ( ClassLib.getResourceAsImage (
173               Fraction.class, FRAME_ICON_FILENAME ) );
174           }
175           catch ( Exception  ex )
176           {
177           }
178    
179           Fraction  fraction = new Fraction ( );
180    
181           jFrame.setContentPane ( fraction );
182    
183           FrameLib.launchJFrameAsDesktopApp (
184             jFrame,
185             new Lifecycle [ ] { fraction },
186             FRAME_SIZE,
187             SHUTDOWN_CONFIRMATION_PROMPT );
188         }
189    
190         //////////////////////////////////////////////////////////////////////
191         //////////////////////////////////////////////////////////////////////
192    
193         public String  getAppletInfo ( )
194         //////////////////////////////////////////////////////////////////////
195         {
196           return INFO;
197         }
198    
199         //////////////////////////////////////////////////////////////////////
200         // interface Lifecycle methods
201         //////////////////////////////////////////////////////////////////////
202    
203         public void  init ( )
204         //////////////////////////////////////////////////////////////////////
205         {
206           System.out.println ( INFO );
207    
208           animatedComponent = new AnimatedComponent ( this, FRAME_RATE );
209    
210           animatedComponent.addComponentListener (
211             new ComponentAdapter ( )
212             {
213               public void  componentResized ( ComponentEvent  componentEvent )
214               {
215                 resetBounds ( );
216               }
217             } );
218    
219           Container  contentPane = getContentPane ( );
220    
221           contentPane.setLayout ( new BorderLayout ( ) );
222    
223           contentPane.add ( animatedComponent, BorderLayout.CENTER );
224    
225           validate ( );
226    
227           animatedComponent.init ( );
228    
229           bounds = animatedComponent.getBounds ( );
230    
231           paintBounds0 = new Rectangle ( );
232    
233           paintBounds1 = new Rectangle ( );
234    
235           heroBounds = new Rectangle ( );
236    
237           fractionHeroSprite = new FractionHeroSprite ( );
238    
239           animatedComponent.addMouseListener ( fractionHeroSprite );
240    
241           animatedComponent.addMouseMotionListener ( fractionHeroSprite );
242    
243           URL  bongoAudioURL = getClass ( ).getClassLoader ( )
244             .getResource ( BONGO_AUDIO_FILENAME );
245    
246           bongoAudioClip = Applet.newAudioClip ( bongoAudioURL );
247    
248           URL  popAudioURL = getClass ( ).getClassLoader ( )
249             .getResource ( POP_AUDIO_FILENAME );
250    
251           popAudioClip = Applet.newAudioClip ( popAudioURL );
252    
253           //
254    
255           fractionQuestion = new FractionQuestion ( FLOORS );
256    
257           questionTextSprite = new TextSprite ( TITLE );
258    
259           questionTextSprite.setColor ( QUESTION_COLOR );
260    
261           answerTextSprite = new TextSprite ( INIT_ANSWER_TEXT );
262    
263           answerTextSprite.setColor ( ANSWER_COLOR );
264    
265           answerTextSprite.setBackgroundColor ( BACKGROUND_COLOR );
266    
267           scoreTextSprite = new TextSprite ( "Score:  0" );
268    
269           scoreTextSprite.setColor ( SCORE_COLOR );
270         }
271    
272         public void  start   ( ) { animatedComponent.start   ( ); }
273    
274         public void  stop    ( ) { animatedComponent.stop    ( ); }
275    
276         public void  destroy ( ) { animatedComponent.destroy ( ); }
277    
278         //////////////////////////////////////////////////////////////////////
279         // interface ComponentAnimator methods
280         //////////////////////////////////////////////////////////////////////
281    
282         public void  update ( JComponent  component )
283         //////////////////////////////////////////////////////////////////////
284         {
285           if ( !boundsInitialized )
286           {
287             resetBounds ( );
288           }
289    
290           heroBounds.setBounds ( bounds );
291    
292           fractionHeroSprite.getPaintBounds ( paintBounds0 );
293    
294           boolean  crossedDoor = false;
295    
296           for ( int  j = 0; j < spriteVelocity; j++ )
297           {
298             int  x = fractionHeroSprite.getX ( );
299    
300             int  y = fractionHeroSprite.getY ( );
301    
302             for ( int  i = 0; i < fractionDoors.length; i++ )
303             {
304               FractionDoor  fractionDoor = fractionDoors [ i ];
305    
306               if ( fractionDoor.contains ( x, y ) )
307               {
308                 crossedDoor = true;
309    
310                 int  localHorizontalMovementY
311                   = fractionDoor.getHorizontalMovementY ( );
312    
313                 int  localVerticalMovementX
314                   = fractionDoor.getVerticalMovementX ( );
315    
316                 fractionHeroSprite.setHorizontalMovementY (
317                   localHorizontalMovementY );
318    
319                 fractionHeroSprite.setVerticalMovementX (
320                   localVerticalMovementX );
321    
322                 fractionHeroSprite.setY ( localHorizontalMovementY );
323    
324                 fractionHeroSprite.setX ( localVerticalMovementX );
325    
326                 if ( !fractionQuestion.getFirst ( ) )
327                 {
328                   if ( ( fractionQuestion.getFirstDenominator ( )
329                     == fractionDoor.getDenominator ( ) )
330                     && ( fractionQuestion.getFirstNumerator ( )
331                     == fractionDoor.getNumerator ( ) ) )
332                   {
333                     fractionQuestion.setFirst ( true );
334    
335                     fractionDoor.setOpen ( );
336    
337                     popAudioClip.play ( );
338    
339                     setHighlighted (
340                       fractionQuestion.getFirstAnswerNumerator ( ),
341                       fractionQuestion.getCommonDenominator ( ) );
342                   }
343                 }
344    
345                 if ( fractionQuestion.getFirst ( )
346                   && !fractionQuestion.getSecond ( ) )
347                 {
348                   if ( ( fractionQuestion.getCommonDenominator ( )
349                     == fractionDoor.getDenominator ( ) )
350                     && ( fractionQuestion.getFirstAnswerNumerator ( )
351                     == fractionDoor.getNumerator ( ) ) )
352                   {
353                     fractionQuestion.setSecond ( true );
354    
355                     fractionDoor.setOpen ( );
356    
357                     popAudioClip.play ( );
358    
359                     setHighlighted (
360                       fractionQuestion.getThirdAnswerNumerator ( ),
361                       fractionQuestion.getCommonDenominator ( ) );
362                   }
363                 }
364    
365                 if ( fractionQuestion.getSecond ( )
366                   && !fractionQuestion.getThird ( ) )
367                 {
368                   if ( ( fractionQuestion.getCommonDenominator ( )
369                     == fractionDoor.getDenominator ( ) )
370                     && ( fractionQuestion.getThirdAnswerNumerator ( )
371                     == fractionDoor.getNumerator ( ) ) )
372                   {
373                     fractionQuestion.setThird ( true );
374    
375                     fractionDoor.setOpen ( );
376    
377                     popAudioClip.play ( );
378    
379                     setHighlighted (
380                       fractionQuestion.getThirdNumerator ( ),
381                       fractionQuestion.getThirdDenominator ( ) );
382                   }
383                 }
384    
385                 if ( fractionQuestion.getThird ( )
386                   && !fractionQuestion.getFourth ( ) )
387                 {
388                   if ( ( fractionQuestion.getThirdDenominator ( )
389                     == fractionDoor.getDenominator ( ) )
390                     && ( fractionQuestion.getThirdNumerator ( )
391                     == fractionDoor.getNumerator ( ) ) )
392                   {
393                     fractionQuestion.setFourth ( true );
394    
395                     bongoAudioClip.play ( );
396    
397                     scoreTextSprite.setText ( "Score:  " + ++score );
398    
399                     answerTextSprite.setText (
400                       fractionQuestion.getFirstNumerator ( )
401                       + "/"
402                       + fractionQuestion.getFirstDenominator ( )
403                       + " + "
404                       + fractionQuestion.getSecondNumerator ( )
405                       + "/"
406                       + fractionQuestion.getSecondDenominator ( )
407                       + " = " 
408                       + ( fractionQuestion.getFourth ( )
409                       ? fractionQuestion.getThirdNumerator ( ) + "/"
410                       + fractionQuestion.getThirdDenominator ( ) : "?/?" ) );
411    
412                     reset ( );
413                   }
414                 }
415    
416                 break;
417               }
418             }
419    
420             fractionHeroSprite.update ( component );
421           }
422    
423           if ( crossedDoor )
424           {
425             component.repaint ( );
426           }
427           else
428           {
429             fractionHeroSprite.getPaintBounds ( paintBounds1 );
430    
431             paintBounds1.add ( paintBounds0 );
432    
433             component.repaint ( paintBounds1 );
434           }
435    
436           String  firstAnswer
437             = fractionQuestion.getFirst ( )
438               ? ( fractionQuestion.getFirstAnswerNumerator ( ) + "/"
439               + fractionQuestion.getCommonDenominator ( ) )
440               : "?/?";
441    
442           String  secondAnswer
443             = fractionQuestion.getSecond ( )
444               ? ( fractionQuestion.getSecondAnswerNumerator ( ) + "/"
445               + fractionQuestion.getCommonDenominator ( ) )
446               : "?/?";
447    
448           String  thirdAnswer
449             = fractionQuestion.getSecond ( )
450               ? ( fractionQuestion.getThirdAnswerNumerator ( ) + "/"
451               + fractionQuestion.getCommonDenominator ( ) )
452               : "?/?";
453    
454           questionTextSprite.setText ( 
455             fractionQuestion.getFirstNumerator ( )
456             + "/"
457             + fractionQuestion.getFirstDenominator ( )
458             + " + "
459             + fractionQuestion.getSecondNumerator ( )
460             + "/"
461             + fractionQuestion.getSecondDenominator ( )
462             + " = " 
463             + firstAnswer
464             + " + "
465             + secondAnswer
466             + " = "
467             + thirdAnswer
468             + ( ( fractionQuestion.getThirdNumerator ( )
469                   == fractionQuestion.getThirdAnswerNumerator ( ) )
470               && ( fractionQuestion.getThirdDenominator ( )
471                    == fractionQuestion.getCommonDenominator ( ) )
472               ? ""
473               : " = ?/?" ) );
474         }
475    
476         public void  paint (
477           JComponent  component,
478           Graphics2D  graphics2D )
479         //////////////////////////////////////////////////////////////////////
480         {
481           graphics2D.setColor ( BACKGROUND_COLOR );
482    
483           graphics2D.fillRect ( 0, 0, bounds.width, bounds.height );
484    
485           graphics2D.setColor ( MOVEMENT_LINE_COLOR );
486    
487           int  verticalMovementX
488             = fractionHeroSprite.getVerticalMovementX ( );
489    
490           int  horizontalMovementY
491             = fractionHeroSprite.getHorizontalMovementY ( );
492    
493           graphics2D.drawLine (
494             verticalMovementX, 0, verticalMovementX, bounds.height - 1);
495    
496           for ( int  i = 0; i < fractionDoors.length; i++ )
497           {
498             fractionDoors [ i ].paint ( component, graphics2D );
499           }
500    
501           for ( int  i = 1; i <= FLOORS; i++ )
502           {
503             int  y = i * floorHeight;
504    
505             graphics2D.setColor ( FLOOR_COLOR );
506    
507             graphics2D.drawLine ( 0, y, bounds.width, y );
508           }
509    
510           graphics2D.setColor ( MOVEMENT_LINE_COLOR );
511    
512           graphics2D.drawLine (
513             0,
514             horizontalMovementY,
515             bounds.width - 1,
516             horizontalMovementY );
517    
518           questionTextSprite.paint ( component, graphics2D );
519    
520           answerTextSprite.paint ( component, graphics2D );
521    
522           scoreTextSprite.paint ( component, graphics2D );
523    
524           fractionHeroSprite.paint ( component, graphics2D );
525         }
526    
527         //////////////////////////////////////////////////////////////////////
528         //////////////////////////////////////////////////////////////////////
529    
530         private void  resetBounds ( )
531         //////////////////////////////////////////////////////////////////////
532         {
533           animatedComponent.getBounds ( bounds );
534    
535           score = 0;
536    
537           scoreTextSprite.setText ( "Score:  0" );
538    
539           answerTextSprite.setText ( INIT_ANSWER_TEXT );
540    
541           // ( 600 / 5 ) / 30 = 4 pixels per frame = 120 pixels per second
542    
543           spriteVelocity
544             = ( int ) Math.ceil ( ( bounds.width / 5 ) / FRAME_RATE );
545    
546           floorHeight = bounds.height / ( FLOORS + 1 );
547    
548           FontLib.setMaxFont (
549             animatedComponent,
550             "Score:  " + Integer.toString ( Integer.MAX_VALUE ),
551             FONT_NAME_SCORE,
552             FONT_STYLE,
553             bounds.width / 2,
554             floorHeight );
555    
556           scoreTextSprite.setFont ( animatedComponent.getFont ( ) );
557    
558           answerTextSprite.setFont ( animatedComponent.getFont ( ) );
559    
560           scoreTextSprite.setY ( floorHeight * FLOORS + floorHeight );
561    
562           questionTextSprite.setY ( 3 * floorHeight / 4 );
563    
564           answerTextSprite.setY ( floorHeight * FLOORS + floorHeight );
565    
566           FontLib.setMaxFont (
567             animatedComponent,
568             "?/? + ?/? = ?/? + ?/? = ?/? = ?/?",
569             FONT_NAME_SCORE,
570             FONT_STYLE,
571             3 * bounds.width / 4,
572             floorHeight );
573    
574           questionTextSprite.setFont ( animatedComponent.getFont ( ) );
575    
576           int  doorHeight = ( int ) ( floorHeight * 0.9 );
577    
578           int  doorSpacing = bounds.width / ( FLOORS + 1 );
579    
580           int  doorWidth
581             = ( int ) ( doorHeight / MathConstants.GOLDEN_RATIO );
582    
583           fractionHeroSprite.setDiameter ( doorHeight );
584    
585           int  doorOffset = ( doorSpacing - doorWidth ) / 2;
586    
587           questionTextSprite.setX ( doorWidth + 2 * doorOffset );
588    
589           answerTextSprite.setX (
590             bounds.width / 2 + doorWidth / 2 + doorOffset );
591    
592           scoreTextSprite.setX ( doorWidth + 2 * doorOffset );
593    
594           FontLib.setMaxFont (
595             animatedComponent,
596             Integer.toString ( FLOORS ),
597             FONT_NAME,
598             FONT_STYLE,
599             doorWidth,
600             doorHeight / 2 );
601    
602           java.util.List  fractionDoorList = new ArrayList ( );
603    
604           Graphics  graphics = animatedComponent.getGraphics ( );
605    
606           FontMetrics  fontMetrics = graphics.getFontMetrics ( );
607    
608           for ( int  i = 1; i <= FLOORS; i++ )
609           {
610             int  y = ( i - 1 ) * floorHeight;
611    
612             Rectangle2D  denominatorTextBounds = fontMetrics.getStringBounds (
613               Integer.toString ( i ), graphics );
614    
615             int  denominatorY
616               = y + floorHeight - doorHeight / 2
617               - ( int ) denominatorTextBounds.getY ( );
618    
619             for ( int  j = 0; j <= i; j++ )
620             {
621               int  x = ( j * doorSpacing * FLOORS ) / i + doorOffset;
622    
623               Rectangle  rectangle = new Rectangle (
624                 x,
625                 y + floorHeight - doorHeight,
626                 doorWidth,
627                 doorHeight );
628    
629               Rectangle2D  numeratorTextBounds
630                 = fontMetrics.getStringBounds (
631                 Integer.toString ( j ), graphics );
632    
633               int  numeratorX = ( int )
634                 ( x + ( doorWidth - numeratorTextBounds.getWidth ( ) ) / 2 );
635    
636               int  numeratorY
637                 = y + floorHeight - doorHeight / 2
638                 - ( int ) ( numeratorTextBounds.getHeight ( )
639                 + numeratorTextBounds.getY ( ) );
640    
641               int  denominatorX = x + ( int )
642                 ( ( doorWidth - denominatorTextBounds.getWidth ( ) ) / 2 );
643    
644               fractionDoorList.add (
645                 new FractionDoor (
646                   rectangle,
647                   j,
648                   i,
649                   numeratorX,
650                   numeratorY,
651                   denominatorX,
652                   denominatorY,
653                   x + 4,
654                   y + floorHeight - doorHeight / 2,
655                   x + doorWidth - 4,
656                   y + floorHeight - doorHeight / 2,
657                   y + floorHeight,
658                   x + doorWidth / 2 ) );
659             }
660           }
661    
662           fractionDoors = ( FractionDoor [ ] )
663             fractionDoorList.toArray ( new FractionDoor [ 0 ] );
664    
665           int  verticalMovementX
666             = fractionDoors [ 0 ].getVerticalMovementX   ( );
667    
668           int  horizontalMovementY
669             = fractionDoors [ 0 ].getHorizontalMovementY ( );
670    
671           fractionHeroSprite.setX ( verticalMovementX );
672    
673           fractionHeroSprite.setY ( horizontalMovementY );
674    
675           fractionHeroSprite.setVerticalMovementX ( verticalMovementX );
676    
677           fractionHeroSprite.setHorizontalMovementY ( horizontalMovementY );
678    
679           reset ( );
680    
681           boundsInitialized = true;
682    
683           animatedComponent.repaint ( );
684         }
685    
686         private void  reset ( )
687         //////////////////////////////////////////////////////////////////////
688         {
689           fractionQuestion.reset ( );
690    
691           for ( int  i = 0; i < fractionDoors.length; i++ )
692           {
693             fractionDoors [ i ].reset ( );
694           }
695        
696           setHighlighted (
697             fractionQuestion.getFirstNumerator   ( ),
698             fractionQuestion.getFirstDenominator ( ) );
699    
700           animatedComponent.repaint ( );
701         }
702    
703         private void  setHighlighted (
704           int  numerator,
705           int  denominator )
706         //////////////////////////////////////////////////////////////////////
707         {
708           for ( int  i = 0; i < fractionDoors.length; i++ )
709           {
710             FractionDoor  fractionDoor = fractionDoors [ i ];
711    
712             if ( ( denominator == fractionDoor.getDenominator ( ) )
713               && ( numerator   == fractionDoor.getNumerator   ( ) ) )
714             {
715               fractionDoor.setHighlighted ( );
716    
717               break;
718             }
719           }
720    
721           animatedComponent.repaint ( );
722         }
723    
724         //////////////////////////////////////////////////////////////////////
725         //////////////////////////////////////////////////////////////////////
726         }