home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / Book Chapters / 01 - Your First Game / Dungeon 1 Plus / Dungeon1Plus.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-04-01  |  15.3 KB  |  528 lines  |  [TEXT/MMCC]

  1.  
  2. /*Dungeon 1 Plus – prototype game example for the Mac Game book*/
  3. /*By Ingemar Ragnemalm 1995*/
  4. /**/
  5. /*This game is somewhat similar to MemoryGame, in that it uses a grid, represented*/
  6. /*by an array. The game is a typical (though extremely simplified) dungeon-digging game,*/
  7. /*where the objective is to collect treasures and fight monsters.*/
  8.  
  9. /*Representation:*/
  10. /*The array tileArr holds nearly all information we need. telling what is in each space in the grid.*/
  11. /*Monsters and treasures are only represented this way. In a real game, you may wish to*/
  12. /*keep a list of all monster and treasure positions, to avoid scanning for them and to keep more*/
  13. /*information about each.*/
  14. /*The game also keeps an array that tells what spaces are known to the player (tilesKnown), and*/
  15. /*the player position (playerPosition), so we don't have to scan for it all the time.*/
  16. /*The combat system is extremely simple. If you try moving to an enemy, you have 60% chance to*/
  17. /*hit it and kill it. If an enemy tries to move to you, it has 50% chance to hit, reducing your hit points*/
  18. /*by one.*/
  19. /**/
  20. /*The game ends when the player dies.*/
  21.  
  22. /* This version adds a level randomizer, and lets us play though as many levels
  23. we can until we get killed.*/
  24.  
  25.  
  26. #include <Sound.h>
  27.  
  28. /*Size of the array*/
  29. #define    kArraySizeH 15
  30. #define    kArraySizeV 12
  31.  
  32. /*Size of the tiles*/
  33. #define    kTileSizeH 32
  34. #define    kTileSizeV 32
  35.  
  36. /* A macro for taking the abs of a value */
  37. #define abs(x) (x>0?x:-x)
  38.  
  39. /* All the possible states of a tile (space in the dungeon) */
  40. typedef enum {empty, wall, player, enemy, tempEnemy, gold, exitPos} TileState;
  41.     
  42. /* The window pointer */
  43.         WindowPtr myWindow;
  44.  
  45. /* Arrays describing the dungeon */
  46.  
  47. /* What tiles have we seen? */
  48. Boolean tileKnown[kArraySizeH][kArraySizeV];
  49. /* What does each tile contain? */
  50. TileState tileArray[kArraySizeH][kArraySizeV];
  51.  
  52. /*Variables describing the player:*/
  53.         Point playerPosition;
  54.         short playerHitPoints;
  55.  
  56. /* A boolean telling if we should quit yet or not */
  57.         Boolean gDone = false;
  58.  
  59. /*Pictures*/
  60.         PicHandle floorTile;
  61.         PicHandle playerTile;
  62.         PicHandle enemyTile;
  63.         PicHandle goldTile;
  64.         PicHandle wallTile;
  65.         PicHandle exitTile;
  66.  
  67. /*All 8 directions as vectors*/
  68. Point directionTable[8] = { { 0, 1 },
  69.                           {-1, 1 },
  70.                           {-1, 0 },
  71.                           {-1,-1 },
  72.                           { 0,-1 },
  73.                           { 1,-1 },
  74.                           { 1, 0 },
  75.                           { 1, 1 }};
  76.  
  77.  
  78. /* A function that generates a value in the interval 0..range-1 */
  79.  
  80. static short Rand(short range)
  81. {
  82.     return (Random () & 0x7fff) % range;
  83. }; /*Rand*/
  84.  
  85.  
  86. /* Draw a tile */
  87.  
  88. static void DrawTile(short h, short v)
  89. {
  90.     Rect tileRectangle;
  91.  
  92.     SetRect(&tileRectangle, h * kTileSizeH, v * kTileSizeV, (h + 1) * kTileSizeH, (v + 1) * kTileSizeV);
  93.     if ( tileKnown[h][v] )
  94.         switch ( tileArray[h][v] )
  95.         {
  96.             case empty: 
  97.                 DrawPicture(floorTile, &tileRectangle); break;
  98.             case wall: 
  99.                 DrawPicture(wallTile, &tileRectangle); break;
  100.             case player: 
  101.                 DrawPicture(playerTile, &tileRectangle); break;
  102.             case enemy:
  103.             case tempEnemy: 
  104.                 DrawPicture(enemyTile, &tileRectangle); break;
  105.             case gold: 
  106.                 DrawPicture(goldTile, &tileRectangle); break;
  107.             case exitPos: 
  108.                 DrawPicture(exitTile, &tileRectangle); break;
  109.             default:
  110.                 PaintRect(&tileRectangle);
  111.         }
  112.     else
  113.         PaintRect(&tileRectangle);
  114. } /*DrawTile*/
  115.  
  116.  
  117. /* Set all tiles around the player to be known, and draw them if they were not known before. */
  118.  
  119. static void ShowAroundPlayer()
  120. {
  121.     short h, v;
  122.  
  123.     for ( h = playerPosition.h - 1 ; h <= playerPosition.h + 1 ; h++)
  124.         for ( v = playerPosition.v - 1 ; v <= playerPosition.v + 1 ; v++)
  125.             if ( ! tileKnown[h][v] )
  126.                 {
  127.                     tileKnown[h][v] = true;
  128.                     DrawTile(h, v);
  129.                 }
  130. } /*ShowAroundPlayer*/
  131.  
  132.  
  133. /* The level generation routine */
  134.  
  135. static void CreateLevel()
  136. {
  137.     Rect roomRect[10];
  138.     short room;
  139.     short height, width;
  140.     short h, v, h1, v1, h2, v2;
  141.     short numRooms;
  142.     short numTreasures, numMonsters;
  143.     short i;
  144.  
  145. /*For each tile position, we set the initial state.*/
  146. /**/
  147. /*Here we generate the dungeon randomly, with a rather simple algorithm.*/
  148. /*Most real games use pre-designed levels, preferrably stored in resources.*/
  149.  
  150.  
  151. /*Dungeon generation algorithm:*/
  152. /*We select a number of rooms to be placed, 3-10*/
  153. /*Each room is randomly assigned a size, and then a position.*/
  154. /*In each room we may put monsters and/or treasures.*/
  155. /*From each room except the last, we make a path from the room to the next.*/
  156. /*This guarantees that all rooms are connected.*/
  157.  
  158.  
  159. /*First fill the entire dungeon with walls!*/
  160.         for ( h = 0 ; h < kArraySizeH ; h++)
  161.             for ( v = 0 ; v < kArraySizeV ; v++)
  162.                     tileArray[h][v] = wall;
  163.  
  164.         numRooms = 3 + Rand(4); /*3 to 6 rooms*/
  165.  
  166. /*Create each room*/
  167.         for ( room = 0 ; room <= numRooms - 1 ; room++)
  168.             {
  169.                 height = 1 + Rand(5 - numRooms / 2);
  170.                 width = 1 + Rand(5 - numRooms / 2);
  171.                 roomRect[room].top = 1 + Rand(kArraySizeV - height - 2);
  172.                 roomRect[room].bottom = roomRect[room].top + height;
  173.                 roomRect[room].left = 1 + Rand(kArraySizeH - width - 2);
  174.                 roomRect[room].right = roomRect[room].left + width;
  175.  
  176.                 for ( h = roomRect[room].left ; h <= roomRect[room].right ; h++)
  177.                     for ( v = roomRect[room].top ; v <= roomRect[room].bottom ; v++)
  178.                             tileArray[h][v] = empty;
  179.             };
  180.  
  181. /*Make paths between all rooms*/
  182.         for ( room = 0 ; room <= numRooms - 2 ; room++)
  183.             {
  184. /*We make a path from h1, h2, to h2, v2*/
  185.                 h1 = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  186.                 v1 = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  187.                 h2 = roomRect[room + 1].left + Rand(roomRect[room + 1].right - roomRect[room + 1].left + 1);
  188.                 v2 = roomRect[room + 1].top + Rand(roomRect[room + 1].bottom - roomRect[room + 1].top + 1);
  189.  
  190. /*First move along the h axis*/
  191.                 if ( h1 < h2 )
  192.                     for ( h = h1 ; h <= h2 ; h++)
  193.                             tileArray[h][v1] = empty;
  194.                 else
  195.                     for ( h = h1 ; h >= h2 ; h--)
  196.                             tileArray[h][v1] = empty;
  197. /*And then along the v axis*/
  198.                 if ( v1 < v2 )
  199.                     for ( v = v1 ; v <= v2 ; v++)
  200.                             tileArray[h2][v] = empty;
  201.                 else
  202.                     for ( v = v1 ; v >= v2 ; v--)
  203.                             tileArray[h2][v] = empty;
  204.             };
  205.  
  206. /*Now populate the rooms!*/
  207.         for ( room = 1 ; room <= numRooms - 1 ; room++)
  208.             {
  209.                 numTreasures = Rand(3);        /*0 to 2 treasures*/
  210.                 for ( i = 1 ; i <= numTreasures ; i++)
  211.                     {
  212.                         h = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  213.                         v = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  214.                         tileArray[h][v] = gold;
  215.                     };
  216.                 numMonsters = Rand(2);        /*0 to 1 monsters*/
  217.                 for ( i = 1 ; i <= numMonsters; i++)
  218.                     {
  219.                         h = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  220.                         v = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  221.                         tileArray[h][v] = enemy;
  222.                     };
  223.             };
  224.  
  225. /*Finally, place the player in the first room and the exit in the last. Also check that the exit is*/
  226. /*not the same position as the player! (It can happen, since we don't mak sure that rooms don't*/
  227. /*overlap!)*/
  228.  
  229. /*Player position:*/
  230.         playerPosition.h = roomRect[0].left + Rand(roomRect[0].right - roomRect[0].left + 1);
  231.         playerPosition.v = roomRect[0].top + Rand(roomRect[0].bottom - roomRect[0].top + 1);
  232.         tileArray[playerPosition.h][playerPosition.v] = player;
  233. /*Exit position:*/
  234.         h = roomRect[numRooms - 1].left + Rand(roomRect[numRooms - 1].right - roomRect[numRooms - 1].left + 1);
  235.         v = roomRect[numRooms - 1].top + Rand(roomRect[numRooms - 1].bottom - roomRect[numRooms - 1].top + 1);
  236. /*But please don't overwrite the player with the exit!*/
  237.         if ( h == playerPosition.h )
  238.             if ( v == playerPosition.v )
  239.                 {
  240. /*Try another room:*/
  241.                     h = roomRect[numRooms - 2].left + Rand(roomRect[numRooms - 2].right - roomRect[numRooms - 2].left + 1);
  242.                     v = roomRect[numRooms - 2].top + Rand(roomRect[numRooms - 2].bottom - roomRect[numRooms - 2].top + 1);
  243.  
  244. /*Still failure? Darn. Just take a space next to the player.*/
  245.                     if ( h == playerPosition.h )
  246.                         if ( v == playerPosition.v )
  247.                             if ( h < kArraySizeH )
  248.                                 h = h + 1;
  249.                             else
  250.                                 h = h - 1;
  251.                 };
  252. /*OK, that's enough. Set the exit!*/
  253.         tileArray[h][v] = exitPos;
  254.  
  255. /*All tiles are unknown when we start*/
  256.         for ( h = 0 ; h < kArraySizeH ; h++)
  257.             for ( v = 0 ; v < kArraySizeV ; v++)
  258.                 tileKnown[h][v] = false;
  259. /*…except the ones around the player*/
  260.  
  261. /*Make the spaces around the player known*/
  262.         ShowAroundPlayer();
  263.  
  264. /*Draw all tiles!*/
  265.         for ( h = 0 ; h < kArraySizeH ; h++)
  266.             for ( v = 0 ; v < kArraySizeV ; v++)
  267.                 DrawTile(h, v);
  268. } /*CreateLevel*/
  269.  
  270.  
  271. /* Move an enemy */
  272.  
  273. static void MoveEnemy(short h, short v)
  274. {
  275.         short dist;
  276.         short newh, newv;
  277.         short dir;
  278.  
  279. /*1: decide if we are close to enough to the player to "hear" the player*/
  280.  
  281.     dist = abs(h - playerPosition.h) + abs(v - playerPosition.v); /*City Block distance*/
  282.  
  283. /*2: Make a suggested destination, newh, newv*/
  284.  
  285.     if ( dist < Rand(15) )
  286.         { /*Move towards the player*/
  287.             if ( h < playerPosition.h )
  288.                 newh = h + 1;
  289.             else if ( h > playerPosition.h )
  290.                 newh = h - 1;
  291.             else
  292.                 newh = h;
  293.             if ( v < playerPosition.v )
  294.                 newv = v + 1;
  295.             else if ( v > playerPosition.v )
  296.                 newv = v - 1;
  297.             else
  298.                 newv = v;
  299.         }
  300.     else
  301.         { /*Move randomly*/
  302.             dir = Rand(8);
  303.             newh = h + directionTable[dir].h;
  304.             newv = v + directionTable[dir].v;
  305.         };
  306.  
  307. /*3: Check what is in the destination and take appropriate action (move, fight)*/
  308.  
  309.     switch ( tileArray[newh][newv] )
  310.         {
  311.         case empty: 
  312.                 tileArray[newh][newv] = tempEnemy;    /*We can't use "enemy", since then we might process it again in the same move!*/
  313.                 tileArray[h][v] = empty;
  314.                 DrawTile(newh, newv);
  315.                 DrawTile(h, v);
  316.                 break;
  317.         case player: 
  318.                 if ( Rand(10) > 5 )                            /*Does it hit?*/
  319.                     {                                        /*Enemy hits player!*/
  320.                         playerHitPoints--;                    /*Reduce player hit points*/
  321.                         if ( playerHitPoints > 0 )
  322.                             {                                        /*Player still lives!*/
  323.                                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer hit"), false) )
  324.                                     ;
  325.                             }
  326.                         else
  327.                             {                                /*Player died!*/
  328.                                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer died"), false) )
  329.                                     ;
  330.                                 gDone = true;            /* Game over - quit! */
  331.                             };
  332.                     }
  333.                 else
  334.                     {                                    /*Miss!*/
  335.                         if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer miss"), false) )
  336.                             ;
  337.                     };
  338.                 break;
  339.          case wall:
  340.         case enemy:
  341.         case gold:
  342.         case exitPos:
  343.         case tempEnemy: 
  344.             ;                                            /*Don't move to any of these!*/
  345.     }; /*case*/
  346. } /*MoveEnemy*/
  347.  
  348.  
  349. /* Initialize - create window, load graphics */
  350.  
  351. static void InitDungeon()
  352. {
  353.     Rect windowRectangle;
  354.  
  355. /*Set up the window*/
  356.         SetRect(&windowRectangle, 50, 50, 50 + kArraySizeH * kTileSizeH, 50 + kArraySizeV * kTileSizeV);
  357.         myWindow = NewCWindow(nil, &windowRectangle, "\pDungeon 1 Plus", true, 0, (WindowPtr)-1L, false, 0);
  358.         SetPort(myWindow);
  359.  
  360.         qd.randSeed = TickCount ();    /*Seed the random number generator*/
  361.  
  362. /*Load all pictures*/
  363.         floorTile = GetPicture(128);            /*PICT resource #128.*/
  364.         playerTile = GetPicture(129);            /*PICT resource #129.*/
  365.         enemyTile = GetPicture(130);            /*PICT resource #130.*/
  366.         goldTile = GetPicture(131);                /*PICT resource #131.*/
  367.         wallTile = GetPicture(132);                /*PICT resource #132.*/
  368.         exitTile = GetPicture(133);                /*PICT resource #133.*/
  369. } /*InitDungeon*/
  370.  
  371.  
  372. /* Set up for a new game */
  373.  
  374. static void NewGame()
  375. {
  376. /* Fill in the tileArr array */
  377.         CreateLevel();
  378.  
  379. /* Start with a healthy player */
  380.         playerHitPoints = 5;
  381. }
  382.  
  383.  
  384. /* ValidMove checks if a tile clickedTile is inside the array bounds *and* near the player */
  385.  
  386. static Boolean ValidMove(Point clickedTile)
  387. {
  388. /* Valid tile?*/
  389.     if ( clickedTile.h >= 0 )
  390.         if ( clickedTile.v >= 0 )
  391.             if ( clickedTile.h < kArraySizeH )
  392.                 if ( clickedTile.v < kArraySizeV )
  393. /* OK, we are inside the game area, clicking in some space! Is it next to the player?*/
  394.                     if ( clickedTile.h >= playerPosition.h - 1 )
  395.                         if ( clickedTile.h <= playerPosition.h + 1 )
  396.                             if ( clickedTile.v >= playerPosition.v - 1 )
  397.                                 if ( clickedTile.v <= playerPosition.v + 1 ) 
  398.                                     return true;
  399.     return false;
  400. } /*ValidMove*/
  401.  
  402.  
  403. /* Try to move the player to the position where we clicked. */
  404.  
  405. static void MovePlayer(Point clickedTile)
  406. {
  407.     short h, v;
  408.  
  409. /* Valid move?*/
  410.     if (ValidMove(clickedTile))
  411. /* Yes! What is there? */
  412.     { 
  413.         switch ( tileArray[clickedTile.h][clickedTile.v] )
  414.             {
  415.             case empty:
  416.                     tileArray[playerPosition.h][playerPosition.v] = empty;
  417.                     tileArray[clickedTile.h][clickedTile.v] = player;
  418.                     DrawTile(playerPosition.h, playerPosition.v);
  419.                     DrawTile(clickedTile.h, clickedTile.v);
  420.                     playerPosition = clickedTile;
  421.                     break;
  422.             case gold: 
  423.                     tileArray[playerPosition.h][playerPosition.v] = empty;
  424.                     tileArray[clickedTile.h][clickedTile.v] = player;
  425.                     DrawTile(playerPosition.h, playerPosition.v);
  426.                     DrawTile(clickedTile.h, clickedTile.v);
  427.                     playerPosition = clickedTile;
  428. /* We could add score here */
  429.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pMoney"), false) )
  430.                         ;
  431.                     break;
  432.             case wall: 
  433.                 SysBeep(1);
  434.                 break;
  435.             case enemy: 
  436.                 if ( Rand(10) > 4 )
  437.                     { /*Hit! Play a "monster died" sound*/
  438.                         if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pEnemy died"), false) )
  439.                             ;
  440.                         /* Walk to the space where the monster was.*/
  441.                         tileArray[playerPosition.h][playerPosition.v] = empty;
  442.                         tileArray[clickedTile.h][clickedTile.v] = player;
  443.                         DrawTile(playerPosition.h, playerPosition.v);
  444.                         DrawTile(clickedTile.h, clickedTile.v);
  445.                         playerPosition = clickedTile;
  446.                     }
  447.                 else
  448.                     { /*Miss! Play the "miss" sound. */
  449.                         if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pEnemy miss"), false) )
  450.                             ;
  451.                     };
  452.                 break;
  453.                 /*attack!*/
  454.             case exitPos: 
  455.                 tileArray[playerPosition.h][playerPosition.v] = empty;
  456.                 tileArray[clickedTile.h][clickedTile.v] = player;
  457.                 DrawTile(playerPosition.h, playerPosition.v);
  458.                 DrawTile(clickedTile.h, clickedTile.v);
  459.                 playerPosition = clickedTile;
  460.                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pNext level"), false) )
  461.                     ;
  462.                 CreateLevel();    /* Don't quit - make a new level instead */
  463.                 break;
  464.         }; /*case*/
  465.     };
  466.  
  467. ShowAroundPlayer();
  468.  
  469. /* Monsters are allowed to move now!*/
  470.  
  471. /* Move all enemies!*/
  472. for ( h = 0 ; h < kArraySizeH ; h++)
  473.     for ( v = 0 ; v < kArraySizeV ; v++)
  474.         if ( tileArray[h][v] == enemy )
  475.             MoveEnemy(h, v);
  476. /* After moving, replace tempEnemy by enemy.*/
  477. for ( h = 0 ; h < kArraySizeH ; h++)
  478.     for ( v = 0 ; v < kArraySizeV ; v++)
  479.         if ( tileArray[h][v] == tempEnemy )
  480.             tileArray[h][v] = enemy;
  481. } /*MovePlayer*/
  482.  
  483.  
  484. /* Standard inits */
  485.  
  486. static void InitToolbox(void) {
  487.     InitGraf (&qd.thePort);
  488.     InitFonts ();
  489.     FlushEvents (everyEvent,0);
  490.     InitWindows ();
  491.     InitMenus ();
  492.     TEInit ();
  493.     InitDialogs (nil);
  494.     InitCursor ();
  495. }
  496.  
  497.  
  498. /* Main program */
  499.  
  500. void main(void)
  501. {
  502.     Point     clickPoint, clickedTile;
  503.  
  504.     InitToolbox();
  505.     InitDungeon();
  506.     NewGame();
  507. /*Initializations done! Run the game loop until the game ends.*/
  508.     do
  509.         {
  510.         if (Button()) {
  511.             GetMouse(&clickPoint);        /* Get the position of the click */
  512.             clickedTile.h = clickPoint.h / kTileSizeH;    /* Convert to grid. */
  513.             clickedTile.v = clickPoint.v / kTileSizeV;
  514.             MovePlayer(clickedTile);    /* Try to move there */
  515.             do {} while (Button());        /* Wait until the mouse click ends */
  516.             };
  517.     } while (!  gDone);
  518.  
  519.     FlushEvents(mDownMask, 0);            /* Get rid of mouse down events! */
  520. } /*Dungeon*/
  521.  
  522.  
  523. /*What's left for making a real game of it?*/
  524. /*- Animations*/
  525. /*- Several levels*/
  526. /*- Faster drawing*/
  527. /*- Asynch sound*/
  528. /*- More objects, i.e. weapons, monsters, treasures…*/