home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1998 May / Pcwk5b98.iso / Borland / Cplus45 / BC45 / BLOCKS.PAK / BLOCKS.CPP next >
C/C++ Source or Header  |  1995-08-29  |  16KB  |  534 lines

  1. //--------------------------------------------------------------------------
  2. // Turbo Blocks -- Copyright (c) 1995, Borland International
  3. //--------------------------------------------------------------------------
  4. #include <owl/applicat.h>
  5. #include <owl/framewin.h>
  6. #include <owl/window.h>
  7. #include <owl/dialog.h>
  8. //#include <mmsystem.h>
  9.  
  10. #include <string.h>
  11.  
  12. #include "blocks.rh"
  13.  
  14. const int GAME_WIDTH = 10;
  15. const int GAME_HEIGHT = 20;
  16. const char IniFilename[] = "BLOCKS.INI";
  17.  
  18. // TBlock is a structure which defines the geometric game piece
  19. //
  20. struct TBlock {
  21.   int  size;          // 2x2, 3x3, or 4x4 (size of square which completely
  22.                       // contains the piece)
  23.   char elements[17];
  24.  
  25.   void rotate();
  26. };
  27.  
  28. // Rotate -- rotate the block (only rotates one direction)
  29. //
  30. void TBlock::rotate() {
  31.   int i,j;
  32.   char tempBlock[17];
  33.  
  34.   switch (size) {
  35.     case 4:
  36.       strcpy(tempBlock,elements);
  37.       for (i=0;i<4;i++)
  38.        for (j=0;j<4;j++)
  39.          tempBlock[i*4+j]=elements[j*4+i];
  40.       strcpy(elements,tempBlock);
  41.       break;
  42.  
  43.     case 3:
  44.       strcpy(tempBlock,elements);
  45.       for (i=0;i<3;i++)
  46.        for (j=0;j<3;j++)
  47.          tempBlock[(2-i)*4+j]=elements[j*4+i];
  48.       strcpy(elements,tempBlock);
  49.       break;
  50.  
  51.     case 2:  // no 2x2 blocks can rotate, so do nothing
  52.       break;
  53.   }
  54. }
  55.  
  56. // Define all the game pieces.
  57.  
  58. TBlock blocks[7] = { {4, " *  "
  59.                          " *  "
  60.                          " *  "
  61.                          " *  "},
  62.                      {3, " ** "
  63.                          " *  "
  64.                          " *  "
  65.                          "    "},
  66.                      {3, "**  "
  67.                          " *  "
  68.                          " *  "
  69.                          "    "},
  70.                      {3, " *  "
  71.                          "*** "
  72.                          "    "
  73.                          "    "},
  74.                      {3, "**  "
  75.                          " ** "
  76.                          "    "
  77.                          "    "},
  78.                      {3, " ** "
  79.                          "**  "
  80.                          "    "
  81.                          "    "},
  82.                      {2, "**  "
  83.                          "**  "
  84.                          "    "
  85.                          "    "} };
  86.  
  87. // these define the pens and brushes to be used for the various
  88. // blocks.  each block type is a different color
  89. //
  90.  
  91. TPen pen[8] = { TPen( TColor( 0, 0, 64 ) ),
  92.                 TPen( TColor( 0, 0, 255 ) ),
  93.                 TPen( TColor( 0, 255, 0 ) ),
  94.                 TPen( TColor( 255, 0, 0 ) ),
  95.                 TPen( TColor( 255,255,0 ) ),
  96.                 TPen( TColor( 255,0,255 ) ),
  97.                 TPen( TColor( 0,255,255 ) ),
  98.                 TPen( TColor( 255,255,255 ) ) };
  99.  
  100. TBrush brush[8] = { TBrush( TColor( 0, 0, 64 ) ),
  101.                     TBrush( TColor( 0, 0, 128 ) ),
  102.                     TBrush( TColor( 0, 128, 0 ) ),
  103.                     TBrush( TColor( 128, 0, 0 ) ),
  104.                     TBrush( TColor( 128,128,0 ) ),
  105.                     TBrush( TColor( 128,0,128 ) ),
  106.                     TBrush( TColor( 0,128,128 ) ),
  107.                     TBrush( TColor( 128,128,128 ) ) };
  108.  
  109.  
  110. // BlocksWindow is the class that actually defines the game.  It is used
  111. // as the client window inside of a TFrameWindow.
  112. //
  113. class TBlocksWindow: public TWindow {
  114.  
  115.   enum GameState { gsGameOver,         // game over, wait for newgame command
  116.                    gsBlockDropping,    // normal mode, blocks are dropping
  117.                    gsPaused };         // game is paused
  118.  
  119.   TMemoryDC *memDC;
  120.   GameState gameState;                       // current state of the game
  121.   TBlock    currentBlock;                    // currently falling block
  122.   int       x,y,                             // current block position
  123.             color;                           // current block color
  124.   int       board[GAME_HEIGHT+1][GAME_WIDTH+2];  // the game grid
  125.                                              // edge to make for easier edge detection
  126.   uint      Timer;                           // ID of game timer
  127.   uint      dropCount;                       // countdown to next time piece falls one row
  128.  
  129.   bool      dropping;                        // is the user holding the 'drop' key down?
  130.  
  131.   int       gameSpeed,blockSize;             // read from .INI file
  132. public:
  133.   TBlocksWindow( TWindow* parent );
  134.   ~TBlocksWindow() {
  135.     char temp[10];
  136.     wsprintf(temp,"%d",gameSpeed);
  137.     WritePrivateProfileString("blocks","gamespeed",temp,IniFilename);
  138.     wsprintf(temp,"%d",blockSize);
  139.     WritePrivateProfileString("blocks","blocksize",temp,IniFilename);
  140.     delete memDC;
  141.   }
  142.   bool EvEraseBkgnd( HDC ) {    // since we paint the entire window, we
  143.     return TRUE;                // don't want windows doing it for us.
  144.   }                             // this will reduce flicker
  145.  
  146.   void SetupWindow();
  147.   void CleanupWindow() {
  148.     KillTimer( Timer );
  149.   }
  150.   void Pause();
  151.   void PauseEnabler( TCommandEnabler& tce ) {
  152.     if (gameState==gsGameOver)
  153.       tce.Enable( false );
  154.     else
  155.       if (gameState==gsPaused)
  156.         tce.SetText("Resume\tAlt+P");
  157.       else
  158.         tce.SetText("Pause\tAlt+P");
  159.   }
  160.   void NewGame();
  161.   void ClearBoard();
  162.   void NewBlock( int blockType );
  163.   void RemoveLines();
  164.   void PlaceBlock();
  165.   bool HitTest( TBlock& block, int x, int y );
  166.   void DrawBlock( TDC& dc, TBlock& block, TPoint& pos );
  167.   void Paint( TDC&, bool, TRect& );
  168.   void EvKeyDown( uint key, uint repeatCount, uint flags );
  169.   void EvKeyUp( uint, uint, uint );
  170.   void EvTimer( uint id );
  171.  
  172.  
  173.   DECLARE_RESPONSE_TABLE( TBlocksWindow );
  174. };
  175.  
  176. DEFINE_RESPONSE_TABLE1( TBlocksWindow, TWindow )
  177.   EV_WM_KEYDOWN,
  178.   EV_WM_KEYUP,
  179.   EV_WM_ERASEBKGND,
  180.   EV_WM_TIMER,
  181.   EV_COMMAND( CM_GAMEPAUSE, Pause ),
  182.   EV_COMMAND( CM_GAME_NEW, NewGame ),
  183.   EV_COMMAND_ENABLE( CM_GAMEPAUSE, PauseEnabler ),
  184. END_RESPONSE_TABLE;
  185.  
  186. // TBlocksWindow -- constructor.  Read the options from the INI file,
  187. // set our game window size, and reset the game board.
  188. //
  189. TBlocksWindow::TBlocksWindow( TWindow* parent )  : TWindow( parent ) {
  190.  
  191.   blockSize = GetPrivateProfileInt( "blocks","blocksize",20,IniFilename);
  192.   gameSpeed = GetPrivateProfileInt( "blocks","gamespeed",10,IniFilename);
  193.  
  194.   Attr.W = blockSize*GAME_WIDTH;
  195.   Attr.H = blockSize*GAME_HEIGHT;
  196.  
  197.   ClearBoard();
  198.  
  199.   dropCount = 0;
  200.   gameState = gsGameOver;
  201.   dropping = false;
  202. }
  203.  
  204. // SetupWindow -- after the window is created, we need to do a bit more
  205. // work.  Create a timer to drive the game, and create a memory DC that
  206. // will be used to draw the blocks off-screen.
  207. //
  208. void TBlocksWindow::SetupWindow() {
  209.   TWindow::SetupWindow();
  210.  
  211.   Timer = 0;
  212.   if (SetTimer( 1, 10 ))
  213.     Timer=1;
  214.  
  215.   TClientDC dc( HWindow );
  216.   memDC = new TMemoryDC( dc );
  217.   memDC->SelectObject( TBitmap( dc, GAME_WIDTH*blockSize, GAME_HEIGHT*blockSize ) );
  218. }
  219.  
  220. // RemoveLines -- checks for completed lines and removes them from
  221. // the game board.  If lines are removed, higher lines are moved down
  222. // to fill in the space.
  223. //
  224. void TBlocksWindow::RemoveLines() {
  225.   int i,j,k,l;
  226.   bool lineFull,linesRemoved;
  227.  
  228.   linesRemoved=false;
  229.   j=GAME_HEIGHT-1;
  230.   while (j>=0) {
  231.     lineFull=true;
  232.     for (i=1;i<=GAME_WIDTH;i++)
  233.       if (board[j][i]==0)
  234.         lineFull=false;
  235.     if (lineFull) {
  236.       linesRemoved=true;
  237.       for (k=j;k>=1;k--)
  238.         for (l=1;l<=GAME_WIDTH;l++)
  239.           board[k][l]=board[k-1][l];
  240.       for (l=1;l<=GAME_WIDTH;l++)
  241.         board[0][l]=0;
  242.     } else
  243.       j--;
  244.   }
  245.   if (linesRemoved) {
  246. //    sndPlaySound( "tada.wav", SND_ASYNC | SND_NODEFAULT );
  247.     Invalidate();
  248.   }
  249. }
  250.  
  251. // NewGame -- start a new game.  First clear the board, then
  252. // add the first block, and change the game state to gsBlockDropping
  253. //
  254. void TBlocksWindow::NewGame() {
  255.   ClearBoard();
  256.   NewBlock( random(7) );
  257.   gameState = gsBlockDropping;
  258.   Invalidate();
  259. }
  260.  
  261. // ClearBoard -- resets the game board to be empty
  262. //
  263. void TBlocksWindow::ClearBoard() {
  264.   int i,j;
  265.  
  266.   for (i=0;i<(GAME_HEIGHT+1);i++)
  267.     for (j=0;j<(GAME_WIDTH+2);j++)
  268.       board[i][j]=0;
  269.   for (i=0;i<(GAME_HEIGHT+1);i++) {
  270.     board[i][0] = -1;
  271.     board[i][GAME_WIDTH+1] = -1;
  272.   }
  273.   for (j=0;j<(GAME_WIDTH+2);j++)
  274.     board[GAME_HEIGHT][j]=-1;
  275. }
  276.  
  277. // EvTimer -- called by the timer we created in SetupWindow.  Depending
  278. // on the game state, do various things.
  279. //
  280. void TBlocksWindow::EvTimer( uint ) {
  281.  
  282.   switch (gameState) {
  283.     case gsGameOver:            // no game in progress, do nothing
  284.       break;
  285.  
  286.     case gsBlockDropping:       // game in progress
  287.       dropCount++;                             // increment drop counter
  288.       if ((dropping) ||
  289.           (dropCount==gameSpeed)) {            // if time to drop
  290.         dropCount=0;                           // reset counter
  291.         y++;                                   // move block down
  292.         if (HitTest( currentBlock, x, y ) ) {  // if it hit something
  293.           y--;                                 // move it back up
  294.           PlaceBlock();                        // make it permanent
  295.           RemoveLines();
  296.           NewBlock( random(7) );
  297.          }
  298.         Invalidate();                          // redraw game board
  299.       }
  300.       break;
  301.  
  302.     case gsPaused:            // game is paused
  303.       break;
  304.   }
  305. }
  306.  
  307. // EvKeyUp/EvKeyDown -- respond to key press/release messages.  For the
  308. // 'drop' key, we set a flag whenever the key is held down.  For the
  309. // left/right/rotate keys, we only keep track of keydown events.
  310. //
  311. void TBlocksWindow::EvKeyUp( uint key, uint, uint ) {
  312.   switch (key) {
  313.     case VK_NUMPAD2:
  314.     case VK_DOWN:
  315.       dropping = false;
  316.       break;
  317.   }
  318. }
  319.  
  320. void TBlocksWindow::EvKeyDown( uint key, uint /*repeat*/, uint /*flags*/ ) {
  321.   if ( gameState == gsPaused ) {
  322.     gameState = gsBlockDropping;
  323.     Invalidate();
  324.     return;
  325.   }
  326.   switch (key) {
  327.  
  328.     // move block left
  329.  
  330.     case VK_NUMPAD4:
  331.     case VK_LEFT:
  332.       x--;
  333.       if (HitTest( currentBlock, x, y ))
  334.         x++;
  335.      else
  336.        Invalidate();
  337.      break;
  338.  
  339.     // move block right
  340.  
  341.     case VK_NUMPAD6:
  342.     case VK_RIGHT:
  343.       x++;
  344.       if (HitTest( currentBlock, x, y ))
  345.         x--;
  346.       else
  347.         Invalidate();
  348.       break;
  349.  
  350.     // turn on fast dropping
  351.  
  352.     case VK_NUMPAD2:
  353.     case VK_DOWN:
  354.       dropping = true;
  355.       break;
  356.  
  357.     // rotate block
  358.  
  359.     case VK_UP:
  360.     case VK_NUMPAD5:
  361.     case VK_SPACE:
  362.       currentBlock.rotate();
  363.       if (HitTest( currentBlock, x, y )) {
  364.         currentBlock.rotate();
  365.         currentBlock.rotate();
  366.         currentBlock.rotate();
  367.       }
  368.       Invalidate();
  369.       break;
  370.   }
  371. }
  372.  
  373. // Pause -- handler for the 'Pause' menu item
  374. //
  375. void TBlocksWindow::Pause() {
  376.   if (gameState==gsBlockDropping)
  377.     gameState=gsPaused;
  378.   else
  379.     if (gameState==gsPaused)
  380.       gameState=gsBlockDropping;
  381.   Invalidate();
  382. }
  383.  
  384. // HitTest -- tests to see if a block overlaps any occupied square
  385. //            on the board
  386. //
  387. bool TBlocksWindow::HitTest( TBlock& block, int x, int y ) {
  388.   int i,j;
  389.   for (i=0;i<4;i++)
  390.     for (j=0;j<4;j++)
  391.       if ( ((x+i)<(GAME_WIDTH+2)) &&
  392.            ((x+i+1)>=0)  &&
  393.            ((y+j)<(GAME_HEIGHT+1)) &&
  394.            ((y+j)>=0) )             // make sure block in question is in range
  395.         if (board[y+j][x+1+i]!=0)   // if the board piece is empty, skip test
  396.           if (block.elements[j*4+i]=='*')
  397.             return true;
  398.   return false;
  399. }
  400.  
  401. // NewBlock -- creates a new block at the top of the screen
  402. //
  403. void TBlocksWindow::NewBlock( int blockType ) {
  404.   currentBlock = blocks[ blockType ];
  405.   color = blockType+1;
  406.   x = 4;
  407.   y = 0;
  408.   // if the new block hits anything on the screen, the game is over
  409.   //
  410.   if (HitTest( currentBlock, x, y )) {
  411.     PlaceBlock();
  412.     Invalidate();
  413.     gameState = gsGameOver;
  414.   }
  415. }
  416.  
  417. // PlaceBlock -- puts the current block permanently into the board array.
  418. //               this function is called when the block reaches the bottom
  419. //               of the game board.
  420. //
  421. void TBlocksWindow::PlaceBlock() {
  422.   int i,j;
  423.   for (i=0;i<4;i++)
  424.     for (j=0;j<4;j++)
  425.       if (currentBlock.elements[j*4+i]=='*')
  426.         board[y+j][x+1+i]=color;
  427.  
  428.   Invalidate();
  429.   RemoveLines();
  430. }
  431.  
  432. // DrawBlock -- draws an individual game piece.
  433. //
  434. void TBlocksWindow::DrawBlock( TDC& dc, TBlock& block, TPoint& pos ) {
  435.   int i,j,size=block.size;
  436.   dc.SelectObject( brush[ color ] );
  437.   dc.SelectObject( pen[ color ] );
  438.   for (i=0;i<size;i++)
  439.     for (j=0;j<size;j++)
  440.       if (block.elements[j*4+i]=='*')
  441.         dc.Rectangle( pos.x*blockSize+i*blockSize,
  442.                       pos.y*blockSize+j*blockSize,
  443.                       pos.x*blockSize+i*blockSize+blockSize,
  444.                       pos.y*blockSize+j*blockSize+blockSize );
  445. }
  446.  
  447. // Paint -- redraws the entire window.  Currently, whenever anything
  448. // on the game board changes, we redraw the entire board.  This could
  449. // obviously be improved, by only redrawing the area around the moving
  450. // block.
  451. //
  452. void TBlocksWindow::Paint( TDC& dc, bool /*erase*/, TRect& /*rect*/ ) {
  453.   int i,j;
  454.  
  455.   // if the game is paused, blank the screen (to prevent cheating!)
  456.  
  457.   if (gameState==gsPaused) {
  458.     memDC->FillRect( GetClientRect(), brush[0] );
  459.     memDC->SetTextColor( TColor( 0, 255, 255 ) );
  460.     memDC->SetBkColor( TColor( 0, 0, 128 ) );
  461.     memDC->TextOut( blockSize*2,blockSize*9, " * * P A U S E D * * " );
  462.     dc.BitBlt( GetClientRect(), *memDC, TPoint(0,0) );
  463.     return;
  464.   }
  465.  
  466.   // clear the memory DC
  467.  
  468.   memDC->FillRect( GetClientRect(), brush[0] );
  469.  
  470.   // draw the permanent blocks
  471.  
  472.   for (j=0;j<GAME_HEIGHT;j++)
  473.     for (i=0;i<GAME_WIDTH;i++)
  474.       if ( board[j][i+1]!=0 ){
  475.         memDC->SelectObject( pen[ board[j][i+1] ] );
  476.         memDC->SelectObject( brush[ board[j][i+1] ] );
  477.         memDC->Rectangle( i*blockSize,j*blockSize,
  478.                          i*blockSize+blockSize,j*blockSize+blockSize );
  479.       }
  480.  
  481.   // display the game over message if the game has ended
  482.  
  483.   if (gameState == gsGameOver) {
  484.     memDC->SetTextColor( TColor( 0, 255, 255 ) );
  485.     memDC->SetBkColor( TColor( 0, 0, 128 ) );
  486.     memDC->TextOut( blockSize*1.5,blockSize*9, "* G A M E   O V E R *");
  487.   }
  488.  
  489.   // if a block is dropping, draw it
  490.  
  491.   if (gameState == gsBlockDropping)
  492.     DrawBlock( *memDC, currentBlock, TPoint( x, y ) );
  493.  
  494.   // now copy the memory DC to the screen.
  495.  
  496.   dc.BitBlt( GetClientRect(), *memDC, TPoint(0,0) );
  497. }
  498.  
  499. // TBlocksApp -- the main application.  InitMainWindow creates the
  500. // framewindow, and inserts a TBlocksWindow as the client.  The app
  501. // also responds to the 'Game|Exit' and 'Help|About' menu items.
  502. //
  503. class TBlocksApp: public TApplication {
  504. public:
  505.   void InitMainWindow() {
  506.      TFrameWindow *fw = new TFrameWindow( 0, "Turbo Blocks",
  507.                                           new TBlocksWindow(0), true );
  508.      fw->Attr.Style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
  509.      fw->AssignMenu( MAIN_MENU );
  510.      fw->Attr.AccelTable = ACCELERATORS_1;
  511.      fw->SetIcon( this, ICON_1 );
  512.  
  513.      SetMainWindow( fw );
  514.   }
  515.   void AboutBox() {
  516.      TDialog( GetMainWindow(), ABOUT_BOX ).Execute();
  517.   }
  518.   void Exit() {
  519.      GetMainWindow()->CloseWindow();
  520.   }
  521.   DECLARE_RESPONSE_TABLE( TBlocksApp );
  522. };
  523. DEFINE_RESPONSE_TABLE1( TBlocksApp, TApplication )
  524.   EV_COMMAND( CM_HELP_ABOUT, AboutBox ),
  525.   EV_COMMAND( CM_GAMEEXIT, Exit ),
  526. END_RESPONSE_TABLE;
  527.  
  528. // the main program
  529.  
  530. int OwlMain( int, char *[] ) {
  531.   randomize();
  532.   return TBlocksApp().Run();
  533. }
  534.