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 }