home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / C / Snippets / NewMaxwell 1.0.2 / NewMaxwell.c < prev    next >
Encoding:
Text File  |  1995-12-01  |  21.9 KB  |  944 lines  |  [TEXT/CWIE]

  1. // NewMaxwell
  2. // by Ken Long <kenlong@netcom.com>
  3. // updated for CW7 on 951201
  4.  
  5. //• Maxwell: particles in box, with gate in middle.  Inspired by the
  6. //• demo by the same name for the Teletype DMD 5620 terminal.
  7.  
  8. typedef struct particle 
  9. {
  10.     long x,y;                //• location.
  11.     long vx, vy;            //• velocity.
  12.     short    pict;            //• which picture this ball is.
  13. } ball;
  14.  
  15. #define    kDrawingRadius        6                        //• radius for drawing (quickdraw units).
  16. #define    kCollisionRadius    28                        //• radius for collision (box units).
  17. #define    kScreenDiameter        (2 * kDrawingRadius)    //• diameter on screen.
  18. #define kMaxBalls            20                        //• must be even.
  19. #define    kSlowestVelocity    225                        //• max v squared for slow ball.
  20.  
  21. #define TWO(X) ((X)+(X))
  22. #define THREE(X) (TWO(X)+(X))
  23.  
  24. static short nslow;
  25. static void enable (MenuHandle menu, short item, short ok);
  26.  
  27. ball Balls[ kMaxBalls ];
  28. short nBalls;
  29. short GateState;                //• != 0 if gate is open.
  30. ControlHandle help;
  31.  
  32. short howWide = 300;                        //• initial size of box.
  33. short howTall = 255;
  34.  
  35. WindowPtr    maxwellWindow;
  36. Rect        dragRect;
  37. Rect        windowBounds = { 40, 100, 335, 430 };
  38.  
  39. MenuHandle    appleMenu, fileMenu, editMenu, widthMenu;
  40.  
  41. enum    {
  42.     appleID = 1,
  43.     fileID,
  44.     editID,
  45.     widthID
  46.     };
  47.  
  48. enum    {
  49.     openItem = 1,
  50.     closeItem,
  51.     quitItem = 4
  52.     };
  53.  
  54. SetUpWindow()
  55. {
  56.     Rect bound; 
  57.     short i;
  58.     GrafPtr saveport;
  59.  
  60.     dragRect = qd.screenBits.bounds;
  61.  
  62.     SetRect( &bound, 40, 100, 430, 335);
  63.     maxwellWindow = NewWindow( 0L, &windowBounds, "\pMaxwell", -1, documentProc, (WindowPtr)-1L, -1, 0L );
  64.  
  65.     InitMoving();
  66.     SetPort( maxwellWindow );
  67.     AddHelpControl( maxwellWindow );
  68.     GetDateTime ((unsigned long*) qd.randSeed);
  69.     SetupUniverse();
  70. }
  71.  
  72. CleanUp ()
  73. {
  74.     DisposeWindow(maxwellWindow);
  75. }
  76.  
  77.  
  78. void SetUpMenus(void)
  79. {
  80.     InsertMenu(appleMenu = NewMenu(appleID, "\p\024"), 0);
  81.     InsertMenu(fileMenu = NewMenu(fileID, "\pFile"), 0);
  82.     InsertMenu(editMenu = NewMenu(editID, "\pEdit"), 0);
  83.     DrawMenuBar();
  84.     AddResMenu(appleMenu, 'DRVR');
  85.     AppendMenu(fileMenu, "\pOpen/O;Close/W;(-;Quit/Q");
  86.     AppendMenu(editMenu, "\pUndo/Z;(-;Cut/X;Copy/C;Paste/V;Clear");
  87. }
  88.  
  89. void AdjustMenus(void)
  90. {
  91.     register WindowPeek wp = (WindowPeek) FrontWindow();
  92.     short kind = wp ? wp->windowKind : 0;
  93.     Boolean DA = kind < 0;
  94.     
  95.     enable(editMenu, 1, DA);
  96.     enable(editMenu, 3, DA);
  97.     enable(editMenu, 4, DA);
  98.     enable(editMenu, 5, DA);
  99.     enable(editMenu, 6, DA);
  100.     
  101.     enable(fileMenu, openItem, !((WindowPeek) maxwellWindow)->visible);
  102.     enable(fileMenu, closeItem, DA || ((WindowPeek) maxwellWindow)->visible);
  103. }
  104.  
  105. static void enable(MenuHandle menu, short item, short ok)
  106. {
  107.     if (ok)
  108.         EnableItem(menu, item);
  109.     else
  110.         DisableItem(menu, item);
  111. }
  112.  
  113. void HandleMenu (long mSelect)
  114. {
  115.     short            menuID = HiWord(mSelect);
  116.     short            menuItem = LoWord(mSelect);
  117.     Str255        name;
  118.     GrafPtr        savePort;
  119.     WindowPeek    frontWindow;
  120.     
  121.     switch (menuID)
  122.     {
  123.         case    appleID:
  124.             GetPort(&savePort);
  125.             GetItem(appleMenu, menuItem, name);
  126.             OpenDeskAcc(name);
  127.             SetPort(savePort);
  128.         break;
  129.     
  130.         case    fileID:
  131.             switch (menuItem)
  132.             {
  133.                 case    openItem:
  134.                     ShowWindow(maxwellWindow);
  135.                     SelectWindow(maxwellWindow);
  136.                 break;
  137.                                   
  138.                 case    closeItem:
  139.                     if ((frontWindow = (WindowPeek) FrontWindow()) == 0L)
  140.                 break;
  141.                 
  142.                 if (frontWindow->windowKind < 0)
  143.                     CloseDeskAcc(frontWindow->windowKind);
  144.                 else 
  145.                     if ((frontWindow = (WindowPeek) maxwellWindow) != NULL)
  146.                             HideWindow(maxwellWindow);
  147.                   break;
  148.                               
  149.                 case    quitItem:
  150.                     ExitToShell();
  151.                 break;
  152.             }
  153.         break;
  154.                   
  155.         case    editID:
  156.             if (!SystemEdit(menuItem-1))
  157.                 SysBeep(5);
  158.         break;
  159.     }
  160. }
  161.  
  162. //• StepBall() moves all the balls
  163. StepBall(GrafPtr wp, short infront)
  164. {
  165.     register short i,j;
  166.  
  167.     nslow = 0;
  168.     SortBalls();
  169.     for ( i = 0; i < nBalls; i++ ) 
  170.     {
  171.         for ( j = i+1; j < nBalls; j++ )
  172.             if ( BallCollide( Balls+i, Balls+j ) )
  173.                 break;
  174.         WallCollide( Balls+i );
  175.         if ( infront )
  176.             SetTheGate( Button() );
  177.     }
  178.     //• If we just draw the balls in order on the screen it will look
  179.     //• bad, since we have them sorted by x.
  180.     for ( i = 0; i < 10; i++ )
  181.     for ( j = i; j < nBalls; j += 10 )
  182.         MoveBall( Balls + j, wp );
  183.  
  184.     if ( nslow < nBalls/3 )
  185.         WallForces( -1 );
  186.     else
  187.     if ( nslow >= (nBalls/3)<<1 )
  188.         WallForces( 1 );
  189.     else
  190.         WallForces( 0 );
  191. }
  192.  
  193. MoveBall(ball *pA, GrafPtr wp)
  194. {
  195.     Rect R;
  196.     register long v2;
  197.     ball before;
  198.     
  199.     before = *pA;        //• save old ball.
  200.     ComputeBall( pA );        //• compute new ball.
  201.     
  202.     v2 = pA->vx * pA->vx + pA->vy * pA->vy;
  203.     
  204.     if ( v2 <= kSlowestVelocity ) nslow++;
  205.     
  206.     pA->pict = (v2 <= kSlowestVelocity ? 0 : 1);
  207.     
  208.     Draw( wp, &before, pA );
  209.     
  210.     return;
  211. }
  212.  
  213. //• Redraw() is called to deal with update events in our window.
  214.  
  215. Redraw(GrafPtr wp)
  216. {
  217.     Rect bound; short i;
  218.  
  219.     BeginUpdate( wp );
  220.     SetRect( &bound, 0, 0, howWide+30, howTall+40 );
  221.     EraseRect( &bound );                //• clear whole window.
  222.     SetRect( &bound, 15, 25, 15+howWide, 25+howTall );
  223.     FrameRect( &bound );                //• draw outer border.
  224.     SetRect( &bound, 13+howWide/2, 25, 17+howWide/2, 25+howTall );
  225.     InvertRect( &bound );                //• draw barrier.
  226.     SetRect( &bound, 13+howWide/2, 24+howTall/3, 17+howWide/2, 26+(howTall+howTall)/3 );
  227.     InvertRect( &bound );                //• remove gate + 1 pixel.
  228.     Toggle();
  229.     GateState = 0;                        //• gate is closed.
  230.     DrawControls( wp );
  231.     
  232.     //• Now draw the balls.  This only works for an even number 
  233.     //• of balls,since Draw() wants two balls.
  234.     for ( i = 0; i < nBalls; i+=2 )
  235.         Draw( wp, Balls + i, Balls + i + 1 );
  236.  
  237.     EndUpdate( wp );
  238.  
  239.     //• Now do the grow icon.
  240.     SetRect( &bound, howWide+15, howTall+25, howWide+30, howTall+40 );
  241.     ClipRect( &bound );
  242.     DrawGrowIcon( wp );
  243.     SetRect( &bound, 0, 0, 31415, 27182 );
  244.     ClipRect( &bound );
  245. }
  246.  
  247. //• Draw() draws two balls in the window.  It is used both to draw the
  248. //• initial balls, with the two balls being different balls, and to
  249. //• draw a ball that has moved.  In the latter case, the two balls are
  250. //• the same ball, once at the old position and once at the new postion.
  251. //• This works since drawing is done in xor mode.
  252. Draw(GrafPtr wp, ball *pA, ball *pB)
  253. {
  254.     register long ax, ay, bx, by;
  255.     
  256.     ax = pA->x >> 2;
  257.     ay = pA->y >> 2;
  258.     
  259.     bx = pB->x >> 2;
  260.     by = pB->y >> 2;
  261.     
  262.     MoveBits( wp, bx-kDrawingRadius + 15, by-kDrawingRadius + 25, ax-kDrawingRadius + 15,
  263.         ay-kDrawingRadius + 25, (long)pB->pict, (long)pA->pict );
  264. }
  265.  
  266. //• set gate to a given state.  bs != 0 means make sure the gate is
  267. //• open, and bs == 0 means make sure it is closed.
  268. SetTheGate( bs ) 
  269. {
  270.     if ( !!bs != GateState ) 
  271.     {
  272.         GateState = !!bs;
  273.         Toggle();
  274.     }
  275. }
  276.  
  277. //• Change the state of the gate on the screen
  278. Toggle() 
  279. {
  280.     Rect bound;
  281.  
  282.     SetRect( &bound, 13 + howWide / 2, 
  283.                      25 + howTall / 3, 
  284.                      17 + howWide / 2, 
  285.                      25 + (howTall + howTall) / 3 );
  286.     InvertRect( &bound );
  287. }
  288.  
  289. //• Generate a random integer in [low,high].  If "contract" is not zero,
  290. //• it skews the distribution to favor numbers nearer the center of
  291. //• the interval
  292.  
  293. rani(low,high,contract)
  294.     short low, high;
  295. {
  296.     register long r;
  297.     register short range;
  298.     
  299.     r = (Random()>>1) + 16384;
  300.     if ( !contract )
  301.         return r * (high-low) / 32768L + low;
  302.     
  303.     range = (high - low) >> 1;
  304.     
  305.     r = r * range;
  306.     r /= 32768;
  307.     r *= Random();
  308.     range = r / 32768;
  309.     
  310.     return ( (low + high) >> 1 ) + range;
  311. }
  312.  
  313. //• Set up balls.
  314.  
  315. SetupUniverse() 
  316. {
  317.     short i; long nb;
  318.     
  319.     PenNormal();
  320.     PenMode( patXor );
  321.     
  322.     nb = (long)howTall * (long)howWide; nb >>= 13; nb <<= 1;
  323.     nBalls = nb + 4;
  324.     if ( nBalls > kMaxBalls )
  325.         nBalls = kMaxBalls;
  326.  
  327.     for ( i = 0; i < nBalls; i++ ) 
  328.     {
  329.         register long a,b;
  330.         
  331.         Balls[i].x  = rani(kCollisionRadius, (howWide<<2)-kCollisionRadius, 0);
  332.         Balls[i].y  = rani(kCollisionRadius, (howTall<<2)-kCollisionRadius, 0);
  333.         Balls[i].vx = a = rani(-40, 40, 1);
  334.         Balls[i].vy = b = rani(-40, 40, 1);
  335.         if ( a*a + b*b <= kSlowestVelocity )
  336.             Balls[i].pict = 0;
  337.         else
  338.             Balls[i].pict = 1;
  339.     }
  340. }
  341.  
  342.  
  343. //• ReSize the window
  344.  
  345. ReSize(WindowPtr wp, Point *mp)
  346. {
  347.     Rect sizeRect; long result;
  348.     
  349.     SetRect( &sizeRect, 150, 80, 2500, 402 );
  350.     result = GrowWindow( wp, *mp, &sizeRect );
  351.     if ( !result )
  352.         return;
  353.     howTall = result >> 16; howTall &= 0xffff;
  354.     howWide = result & 0xffff;
  355.     
  356.     howWide -= 29; howWide -= howWide % 2;
  357.     howTall -= 38; howTall -= howTall % 3;
  358.     
  359.     DisposeControl( help );
  360.     
  361.     SizeWindow( wp, howWide + 30, howTall + 40, 0 );
  362.     
  363.     AddHelpControl( wp );
  364.     
  365.     SetRect( &sizeRect, 0, 0, howWide + 30, howTall + 40 );
  366.     InvalRect( &sizeRect );
  367.     
  368.     SetupUniverse();
  369.     Redraw( wp );
  370. }
  371.  
  372.  
  373. //• Sort balls by x co-ordinate.  The first time this is called, it has
  374. //• to do a lot of work, but on subsequent calls, the balls will for the
  375. //• most part be already in order.  We shall use a bubble sort, since
  376. //• it is very fast for an already sorted list.
  377.  
  378. SortBalls() 
  379. {
  380.     register short i, j, flag;
  381.     
  382.     flag = 1;
  383.     for ( i = 0; i < (nBalls - 1) && flag; i++ )
  384.         for ( flag = 0, j = nBalls-1; j > i; --j )
  385.             if ( Balls[j-1].x > Balls[j].x ) 
  386.             {
  387.                 ball temp;
  388.                 flag = 1;
  389.                 temp = Balls[j-1]; Balls[j-1] = Balls[j];
  390.                 Balls[j] = temp;
  391.             }
  392. }
  393.  
  394.  
  395. //• repond to a click in the help button
  396.  
  397. ShowHelp() 
  398. {
  399.     Rect bound;
  400.     WindowPtr wp;
  401.     char *h1, *h2, *h3, *h4;
  402.     char helptext[1024];
  403.     
  404.     SetRect( &bound, 91, 68, 421, 303 );
  405.     wp = NewWindow( 0L, &bound, "\pA", -1, dBoxProc, (WindowPtr)-1L, 0, 0L );
  406.     SetPort( wp );
  407.  
  408.     TextMode( srcXor );
  409.     TextSize( 9 );
  410.     
  411.     SetRect( &bound, 5, 5, 325, 230 );
  412.  
  413.     h1 = "\
  414. Maxwell V2.1 from Mithral Engineering.\r\
  415. This program and its source code are in the public domain.\r\r\
  416. Whenever the Maxwell window is in front, holding down the\
  417.  mouse button opens the gate so that balls may go from one\
  418.  side to the other.\r\r";
  419.      h2 = "\
  420. Try to get all the fast balls ( the black ones ) in the right\
  421.  half of the window, and all the slow ones in the left.\r\r\
  422. Due to roundoff errors, at each collision there is a slight";
  423.     h3 = "\
  424.  net decrease in the total energy of the balls.  To balance\
  425.  this, the right wall will become 'hot' if less than one third\
  426.  of the balls are fast balls.  When a slow ball hits the hot right";
  427.      h4 = "\
  428.  wall, it will become a very fast ball.  When more than one third\
  429.  of the balls are fast, the right wall will cool off.\r\
  430. --------------------------------------------------------------\r\
  431. Made to run under Code Warrior by Kenneth A. Long on 20 Nov 94.\r\
  432. The shell is basically \"Bullseye,\" and all merged into one .c file.";
  433.  
  434.     strcpy( helptext, h1 );
  435.     strcat( helptext, h2 );
  436.     strcat( helptext, h3 );
  437.     strcat( helptext, h4 );
  438.  
  439.     TextBox( helptext, (long)strlen( helptext ), &bound, teJustLeft );
  440.     
  441.     while ( !Button() )
  442.         ;
  443.     FlushEvents( mDownMask, 0 );
  444.     DisposeWindow( wp );
  445. }
  446.  
  447.  
  448. //• put up the help button
  449.  
  450. AddHelpControl(WindowPtr wp)
  451. {
  452.     Rect bound;
  453.     
  454.     SetRect( &bound, (howWide>>1)-7, 4 , (howWide>>1) + 37, 22 );
  455.     
  456.     help = NewControl( wp, &bound, "\pHelp!", -1,0,0,0, pushButProc, 0L );
  457. }
  458.  
  459.  
  460. //• find out if the user wants help
  461.  
  462. CheckHelp(EventRecord *erp, WindowPtr win)
  463. {
  464.     short part;
  465.     ControlHandle ch;
  466.     
  467.     part = FindControl( erp->where, win, &ch );
  468.     
  469.     if ( part != inButton || ch != help )
  470.         return;
  471.         
  472.      part = TrackControl( ch, erp->where, 0L );
  473.     
  474.      if ( part == inButton )
  475.          ShowHelp();
  476. }
  477.  
  478.  
  479.  
  480. //• move or draw ball on screen.
  481.  
  482. //• There are three off-screen bitmaps.  The first two hold the images of
  483. //• a slow ball and a fast ball.  The third is used for combining the
  484. //• before and after pictures so that a screen update of a moving ball
  485. //• whose new postion overlaps its old position can be done in one
  486. //• screen operation.  This is intended to reduce flicker, by cutting
  487. //• number of copybits calls to the screen from two to one per slow
  488. //• moving ball.  However, it increases the total number of copybits
  489. //• calls, which makes things run slower.
  490.  
  491. //• The use of this third bitmap is enabled by defining SMOOTH.  It will
  492. //• run ~25% slower with SMOOTH defined.
  493.  
  494.  
  495. char slowBits[ 2 * (kScreenDiameter) * ((kScreenDiameter + 15)/16) ];        //• slow bits.
  496. char fastBits[ 2 * (kScreenDiameter) * ((kScreenDiameter + 15)/16) ];        //• fast bits.
  497.  
  498. #ifdef SMOOTH
  499. //• combined bits.
  500. char combBits[ 2 * (2 * kScreenDiameter) * ((2 * kScreenDiameter + 15) / 16) ];    
  501. #endif
  502.  
  503. BitMap slowMap, fastMap            //• bitmaps for slow and fast bits.
  504. #ifdef SMOOTH
  505.     ,combMap                    //• and combined bits.
  506. #endif
  507. ;
  508.  
  509. //• MoveBits() draws two balls.  The first is in the square with corner
  510. //• at Sx, Sy, and side kScreenDiameter.  The second is at Dx and Dy, and has the
  511. //• same size.  Which drawing to use for each is determined by op and 
  512. //• np.
  513.  
  514. MoveBits(GrafPtr win, long Sx, long Sy, long Dx, long Dy, 
  515.                     long op, long np)
  516. {
  517.     register short sx, sy, dx, dy;
  518.     short tx,ty;
  519.     short rt, rl;
  520.     Rect S,D;
  521.  
  522.     //• The pointers to the bits in the bitmaps may have changed if we
  523.     //• have been moved on the heap since we were initialized, so 
  524.     //• we shall fix them.
  525.     slowMap.baseAddr = slowBits;
  526.     fastMap.baseAddr = fastBits;
  527. #ifdef SMOOTH
  528.     combMap.baseAddr = combBits;
  529. #endif
  530.     
  531.     //• convert to integers.
  532.     sx = Sx; 
  533.     sy = Sy; 
  534.     dx = Dx; 
  535.     dy = Dy; 
  536.  
  537.     tx = dx - sx;            //• relative x positions.
  538.     ty = dy - sy;            //• relative y positions.
  539.     
  540.     
  541. #define pict(v) ((v) == 0 ? &slowMap : &fastMap )
  542.  
  543.     SetPort( win );
  544.  
  545. //• If the balls do not overlap, or if SMOOTH is not defined, then we
  546. //• simply want to erase the first ball and draw the second.
  547. #ifdef SMOOTH
  548.     //• If the balls can't possibly overlap, then just draw them 
  549.     //• directly on the screen
  550.     if ( tx < - kDrawingRadius || tx > kDrawingRadius || ty < - kDrawingRadius || ty > kDrawingRadius ) 
  551.     {
  552. #endif
  553.         SetRect( &S, 0, 0, kScreenDiameter, kScreenDiameter );
  554.         SetRect( &D, sx, sy, sx + kScreenDiameter, sy + kScreenDiameter );
  555.         CopyBits( pict(op), &win->portBits, &S, &D, srcXor, 0L );
  556.         SetRect( &D, dx, dy, dx + kScreenDiameter, dy + kScreenDiameter );
  557.         CopyBits( pict(np), &win->portBits, &S, &D, srcXor, 0L );
  558.         return;
  559. #ifdef SMOOTH
  560.     }
  561.     
  562.     //• The balls are close enough that we may combine their 
  563.     //• updates into one CopyBits to the screen.
  564.     
  565.     //• The rest of this is a pain to explain without a diagram,
  566.     //• so figure it out for yourself!
  567.     if ( ty > 0 )
  568.         if ( tx > 0 )    
  569.         { 
  570.             rt = 0;    
  571.             rl = 0; 
  572.         }
  573.         else            
  574.             { 
  575.                 rt = 0;    
  576.                 rl = kScreenDiameter; 
  577.         }
  578.     else
  579.         if ( tx > 0 )    
  580.         { 
  581.             rt = kScreenDiameter; 
  582.             rl = 0; 
  583.         }
  584.         else            
  585.             { 
  586.                 rt = kScreenDiameter; 
  587.                 rl = kScreenDiameter; 
  588.         }
  589.         
  590.     SetRect( &D, 0, 0, 2 * kScreenDiameter, 2 * kScreenDiameter );
  591.     CopyBits( &combMap, &combMap, &D, &D, srcXor, 0L );
  592.     SetRect( &S, 0, 0, kScreenDiameter, kScreenDiameter );
  593.     SetRect( &D, rl, rt, rl+kScreenDiameter, rt+kScreenDiameter );
  594.     CopyBits( pict(op), &combMap, &S, &D, srcCopy, 0L );
  595.     SetRect( &D, rl + tx, rt + ty, rl + tx + kScreenDiameter, rt + ty + kScreenDiameter );
  596.     CopyBits( pict(np), &combMap, &S, &D, srcXor, 0L );
  597.     SetRect( &S, 0, 0, kScreenDiameter * 2, kScreenDiameter * 2 );
  598.     SetRect( &D, sx - rl, sy - rt, sx - rl + 2 * kScreenDiameter, sy - rt + 2 * kScreenDiameter );
  599.     CopyBits( &combMap, &win->portBits, &S, &D, srcXor, 0L );
  600. #endif
  601. }
  602.  
  603. //• Initialize the bitmaps
  604.  
  605. InitMoving() 
  606. {        
  607.     Rect S,D;
  608.     GrafPort port;
  609.  
  610.     //• Get a grafport to do our thing in.
  611.     OpenPort( &port );
  612.     PenNormal();
  613.     
  614.     //• Bitmap for slow bits...
  615.     slowMap.baseAddr = slowBits;
  616.     slowMap.rowBytes = 2*((kScreenDiameter+15)/16);
  617.     slowMap.bounds.top = 0;
  618.     slowMap.bounds.bottom = 2*kScreenDiameter;
  619.     slowMap.bounds.left = 0;
  620.     slowMap.bounds.right = 2*kScreenDiameter;
  621.     
  622.     //• Bitmap for fast bits...
  623.     fastMap.baseAddr = fastBits;
  624.     fastMap.rowBytes = 2*((kScreenDiameter+15)/16);
  625.     fastMap.bounds.top = 0;
  626.     fastMap.bounds.bottom = 2*kScreenDiameter;
  627.     fastMap.bounds.left = 0;
  628.     fastMap.bounds.right = 2*kScreenDiameter;
  629.     
  630.     SetPortBits( &slowMap );        //• prepare to draw slow ball.
  631.     
  632.     SetRect( &S, 0, 0, kScreenDiameter, kScreenDiameter );
  633.     FrameOval( &S );
  634.  
  635.     //• ...risking the taste-police - :)
  636.     MoveTo( 3, 7 ); 
  637.     LineTo( 4, 8 );    
  638.     LineTo( 7, 8 ); 
  639.     LineTo( 8, 7 );
  640.     MoveTo( 4, 4 ); 
  641.     LineTo( 4, 4 );
  642.     MoveTo( 7, 4 ); 
  643.     LineTo( 7, 4 );
  644.  
  645.     SetPortBits( &fastMap );        //• prepare to draw fast ball.
  646.     InvertOval( &S );
  647.     
  648. #ifdef SMOOTH
  649.     //• Bitmap for combined drawings...
  650.     combMap.baseAddr = combBits;
  651.     combMap.rowBytes = 2*((kScreenDiameter+kScreenDiameter+15)/16);
  652.     combMap.bounds.top = 0;
  653.     combMap.bounds.bottom = 2*kScreenDiameter;
  654.     combMap.bounds.left = 0;
  655.     combMap.bounds.right = 2 * kScreenDiameter;
  656. #endif    
  657. }
  658.  
  659.  
  660. //• Do collisions and movement.
  661.  
  662. //• BallCollide() deals with a possible collision between two 
  663. //• specific balls.
  664. //• The first ball will not be to the right of the second ball.  
  665. //• Returns one if the second ball is far enough to the right so 
  666. //• that no balls farther right could collide with the first ball, 
  667. //• else returns zero.
  668.  
  669. BallCollide(ball *pA, ball *pB)
  670. {
  671.     register long k;
  672.     long tAvx, tAvy;
  673.     register long tBx, tBy;
  674.     long tBvx, tBvy;
  675.     long WIDE, TALL;
  676.     
  677.     WIDE = howWide<<2;            //• scale from window to physical co-ords.
  678.     TALL = howTall<<2;
  679.     
  680.     //• Deal with the barrier and the gate.
  681.     if ( TWO(pA->x) <= WIDE && TWO(pB->x) >= WIDE )
  682.         //• gate closed means no collision.
  683.         if ( ! GateState )
  684.             return 0;
  685.         else
  686.             //• If either ball is below gate, no collision.
  687.             if ( THREE(pA->y) < TALL || THREE(pB->y) < TALL )
  688.                 return 0;
  689.         else
  690.             //• If either ball is above gate, no collision.
  691.             if (THREE(pA->y) > 
  692.                 TWO(TALL) || 
  693.                 THREE(pB->y) > 
  694.                 TWO(TALL) )
  695.             return 0;
  696.  
  697.     //• Shift to A's co-ordinate system.
  698.     tBx = pB->x - pA->x;
  699.     tBy = pB->y - pA->y;
  700.     
  701.     tBvx = pB->vx - pA->vx;    
  702.     tAvx = 0;
  703.     
  704.     tBvy = pB->vy - pA->vy;    
  705.     tAvy = 0;
  706.     
  707.     //• See if the balls are close enough to have collided.
  708.     if ( tBx > TWO(kCollisionRadius) )
  709.         return 1;
  710.     if ( tBx * tBx + tBy * tBy > (kCollisionRadius * kCollisionRadius<<2) )
  711.         return 0;
  712.     
  713.     k = tBx * tBvx + tBy * tBvy;
  714.     
  715.     //• Make sure they are going towards each other.
  716.     if ( k > -1 )
  717.         return 0;
  718.         
  719.     k = ( tBy * tBvx - tBx * tBvy ) / k;
  720.     
  721.     tAvx = ( tBvx - k * tBvy ) / ( 1 + k*k );
  722.     tAvy = ( k * tBvx + tBvy ) / ( 1 + k*k );
  723.     
  724.     tBvx =  k * tAvy;
  725.     tBvy = -k * tAvx;
  726.     
  727.     pB->vx = pA->vx + tBvx;
  728.     pB->vy = pA->vy + tBvy;
  729.     pA->vx += tAvx;
  730.     pA->vy += tAvy;
  731.     
  732.     return 0;
  733. }
  734.  
  735. //• Because the calculations above use longs instead of floats, we have a
  736. //• lot of round off error.  This seems to manifest itself by causing the
  737. //• balls to slow down over time.  We use the walls to correct this.
  738.  
  739. //• If "we" is greater than zero, we are attempting to add energy to the
  740. //• system.  We do this by looking for slow balls bouncing off the right
  741. //• wall.  When we find such a ball, we give it a swift kick towards the
  742. //• left.
  743.  
  744. //• If "we" is less than zero, then we are trying to remove energt from
  745. //• the system for some reason.  In this case, the outer walls become
  746. //• slightly sticky, with the ball slowing down by abs(we) perpendicular
  747. //• to the wall.
  748.  
  749. //• This stuff is done in WallCollide().
  750.  
  751. static short we = 0;            //• wall energy factor.
  752.  
  753. WallForces( i ) 
  754. {
  755.     we = i;
  756. }
  757.  
  758.  
  759. //• WallCollide() checks for collisions between a ball walls or the gate.
  760. WallCollide(ball *pA)
  761. {
  762.     register long WIDE, TALL;
  763.     
  764.     WIDE = howWide<<2; TALL = howTall<<2;
  765.     
  766.     if ( (pA->x <= kCollisionRadius && pA->vx < 0)
  767.       || (pA->x >= WIDE-kCollisionRadius && pA->vx > 0) ) 
  768.     {
  769.         pA->vx = -pA->vx;
  770.         if ( we > 0 ) 
  771.         {
  772.             if ( pA->vx < 0 && pA->pict == 0 && pA->x >= WIDE-kCollisionRadius)
  773.                 pA->vx -= 30;                //• swift kick.
  774.         } 
  775.         else 
  776.             {
  777.                 if ( pA->vx > 0 )
  778.                     pA->vx += we;
  779.                 else
  780.                     pA->vx -= we;
  781.         }
  782.     }
  783.     if ((pA->y <= kCollisionRadius && pA->vy < 0) ||
  784.         (pA->y >= TALL-kCollisionRadius && pA->vy > 0) ) 
  785.     {
  786.         pA->vy = -pA->vy;
  787.         if ( we < 0 ) 
  788.         {
  789.             if ( pA->vy > 0 )
  790.                 pA->vy += we;
  791.             else
  792.                 pA->vy -= we;
  793.         }
  794.  
  795.     }
  796.     
  797.     //• if the ball is on the same level as the gate, and the gate 
  798.     //• is open, there is nothing for the ball to hit on the barrier.
  799.  
  800.     if ( TALL < THREE(pA->y) && THREE(pA->y) < TWO(TALL) && GateState )
  801.         return;
  802.         
  803.     WIDE >>= 1;            //• location of the barrier.
  804.  
  805.     //• see if the ball hits the barrier.
  806.     if ( pA->x <= WIDE && pA->vx > 0 ) 
  807.     {
  808.         if ( pA->x + kCollisionRadius >= WIDE || pA->x + pA->vx > WIDE )
  809.             pA->vx = -pA->vx;
  810.     } 
  811.     else
  812.         if ( pA->x >= WIDE &&  pA->vx < 0 )
  813.             if ( pA->x - kCollisionRadius <= WIDE || pA->x + pA->vx < WIDE )
  814.                 pA->vx = -pA->vx;
  815. }
  816.  
  817. ComputeBall(ball *pA)
  818. {
  819.     register long vx, vy;
  820.     pA->x += vx = pA->vx;
  821.     pA->y += vy = pA->vy;
  822.  
  823.     
  824.     //• check for stalled balls, and offer them a chance to get going again.
  825.     if ( vx == 0 && vy == 0 ) 
  826.     {
  827.         if ( Random() > 0 )
  828.             pA->vx = Random() > 0 ? 1 : -1;
  829.         if ( Random() > 0 )
  830.             pA->vy = Random() > 0 ? 1 : -1;
  831.     }
  832. }
  833.  
  834. void InitMacintosh(void)
  835. {
  836.     MaxApplZone();
  837.     
  838.     InitGraf(&qd.thePort);
  839.     InitFonts();
  840.     FlushEvents(everyEvent, 0);
  841.     InitWindows();
  842.     InitMenus();
  843.     TEInit();
  844.     InitDialogs(0L);
  845.     InitCursor();
  846. }
  847.  
  848. void HandleMouseDown (EventRecord    *theEvent)
  849. {
  850.     WindowPtr    theWindow;
  851.     short            windowCode = FindWindow (theEvent->where, &theWindow);
  852.     
  853.     switch (windowCode)
  854.     {
  855.         case inSysWindow: 
  856.             SystemClick (theEvent, theWindow);
  857.         break;
  858.             
  859.         case inMenuBar:
  860.             AdjustMenus();
  861.             HandleMenu(MenuSelect(theEvent->where));
  862.         break;
  863.             
  864.         case inDrag:
  865.             if (theWindow == maxwellWindow)
  866.             DragWindow(maxwellWindow, theEvent->where, &dragRect);
  867.         break;
  868.             
  869.         case inContent:
  870.             if (theWindow == maxwellWindow)
  871.             {                
  872.                     GlobalToLocal( &theEvent->where );
  873.                     
  874.                     //• check grow icon...
  875.                     if ( 15 + howWide < theEvent->where.h
  876.                         && theEvent->where.h < 30 + howWide
  877.                         && 25 + howTall < theEvent->where.v &&
  878.                         theEvent->where.v < 40 + howTall) 
  879.                     {
  880.                         LocalToGlobal( &theEvent->where );
  881.                         ReSize( maxwellWindow, &theEvent->where );
  882.                     } 
  883.                     else
  884.                         //• see if there is any reason to check the 
  885.                         //• help button...
  886.                         if ( theEvent->where.v <= 25 )
  887.                             //• yup, there is...
  888.                             CheckHelp( theEvent, maxwellWindow );
  889.             }
  890.         break;
  891.             
  892.         case inGoAway:
  893.             if (theWindow == maxwellWindow && 
  894.                 TrackGoAway(maxwellWindow, theEvent->where))
  895.                 HideWindow(maxwellWindow);
  896.         break;
  897.     }
  898. }
  899.  
  900. void HandleEvent(void)
  901. {
  902.     short ok, infront = 1;
  903.     EventRecord    theEvent;
  904.  
  905.     HiliteMenu(0);
  906.     SystemTask ();        /* Handle desk accessories */
  907.  
  908.     StepBall( maxwellWindow, infront );
  909.  
  910.     ok = GetNextEvent (everyEvent, &theEvent);
  911.     if (ok)
  912.     switch (theEvent.what)
  913.     {
  914.         case mouseDown:
  915.             HandleMouseDown(&theEvent);
  916.         break;
  917.             
  918.         case keyDown: 
  919.         case autoKey:
  920.             if ((theEvent.modifiers & cmdKey) != 0)
  921.             {
  922.                 AdjustMenus();
  923.                 HandleMenu(MenuKey((char) (theEvent.message & charCodeMask)));
  924.             }
  925.         break;
  926.             
  927.         case activateEvt:
  928.         case updateEvt:
  929.             SetTheGate(0);
  930.             Redraw( maxwellWindow );
  931.             HiliteControl( help, infront ? 0 : 255 );
  932.         break;
  933.     }
  934. }
  935.  
  936. void main( void)
  937. {
  938.     InitMacintosh();
  939.     SetUpMenus();
  940.     SetUpWindow();
  941.     for (;;)
  942.         HandleEvent();
  943. }
  944.