Subversion Repositories Games.Rick Dangerous

Rev

Rev 7 | Rev 10 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * src/game.c
  3.  *
  4.  * Copyright (C) 1998-2002 BigOrno (bigorno@bigorno.net). All rights reserved.
  5.  *
  6.  * The use and distribution terms for this software are contained in the file
  7.  * named README, which can be found in the root of this distribution. By
  8.  * using this software in any fashion, you are agreeing to be bound by the
  9.  * terms of this license.
  10.  *
  11.  * You must not remove this notice, or any other, from this software.
  12.  */
  13.  
  14. #include <stdlib.h>
  15.  
  16. #include "system.h"
  17. #include "game.h"
  18.  
  19. #include "draw.h"
  20. #include "maps.h"
  21. #include "ents.h"
  22. #include "e_rick.h"
  23. #include "e_sbonus.h"
  24. #include "e_them.h"
  25. #include "screens.h"
  26. #include "rects.h"
  27. #include "scroller.h"
  28. #include "control.h"
  29.  
  30.  
  31. /*
  32.  * local typedefs
  33.  */
  34. typedef enum
  35. {
  36.    INIT_GAME, INIT_BUFFER,
  37.    INTRO_MAIN, INTRO_MAP,
  38.    PAUSE_PRESSED1, PAUSE_PRESSED1B, PAUSED, PAUSE_PRESSED2,
  39.    PLAY0, PLAY1, PLAY2, PLAY3,
  40.    CHAIN_SUBMAP, CHAIN_MAP, CHAIN_END,
  41.    SCROLL_UP, SCROLL_DOWN,
  42.    RESTART, GAMEOVER, GETNAME, EXIT
  43. } game_state_t;
  44.  
  45.  
  46. /*
  47.  * global vars
  48.  */
  49. U8 game_period = 0;
  50. U8 game_waitevt = FALSE;
  51. rect_t *game_rects = NULL;
  52.  
  53. U8 game_lives = 0;
  54. U8 game_bombs = 0;
  55. U8 game_bullets = 0;
  56. U32 game_score = 0;
  57.  
  58. U16 game_map = 0;
  59. U16 game_submap = 0;
  60.  
  61. U8 game_dir = 0;
  62. U8 game_chsm = FALSE;
  63.  
  64. U8 want_infinitelives = FALSE;
  65.  
  66.  
  67. hscore_t game_hscores[8] =
  68. {
  69.    { 8000, "DANGERSTU@" },
  70.    { 7000, "SIMES@@@@@" },
  71.    { 6000, "KEN@T@ZEN@" },
  72.    { 5000, "BOBBLE@@@@" },
  73.    { 4000, "GREG@LAA@@" },
  74.    { 3000, "TELLY@@@@@" },
  75.    { 2000, "CHIGLET@@@" },
  76.    { 1000, "ANDYSPLEEN" }
  77. };
  78.  
  79. sound_t *WAV_GAMEOVER;
  80. sound_t *WAV_SBONUS2;
  81. sound_t *WAV_BULLET;
  82. sound_t *WAV_BOMBSHHT;
  83. sound_t *WAV_EXPLODE;
  84. sound_t *WAV_STICK;
  85. sound_t *WAV_WALK;
  86. sound_t *WAV_CRAWL;
  87. sound_t *WAV_JUMP;
  88. sound_t *WAV_PAD;
  89. sound_t *WAV_BOX;
  90. sound_t *WAV_BONUS;
  91. sound_t *WAV_SBONUS1;
  92. sound_t *WAV_DIE;
  93. sound_t *WAV_ENTITY[10];
  94.  
  95.  
  96. /*
  97.  * local vars
  98.  */
  99. static U8 isave_frow;
  100. static game_state_t game_state;
  101. static sound_t *music_snd;
  102.  
  103.  
  104. /*
  105.  * prototypes
  106.  */
  107. static void frame (void);
  108. static void init (void);
  109. static void play0 (void);
  110. static void play3 (void);
  111. static void restart (void);
  112. static void isave (void);
  113. static void irestore (void);
  114. static void loaddata (void);
  115. static void freedata (void);
  116.  
  117.  
  118. /*
  119.  * Music
  120.  */
  121. void game_setmusic (char *name, U8 loop)
  122. {
  123.    U8 channel;
  124.  
  125.    if (music_snd)
  126.       game_stopmusic ();
  127.  
  128.    music_snd = syssnd_load (name);
  129.  
  130.    if (music_snd)
  131.    {
  132.       music_snd->dispose = TRUE; /* music is always "fire and forget" */
  133.       channel = syssnd_play(music_snd, loop);
  134.    }
  135. }
  136.  
  137.  
  138. void game_stopmusic (void)
  139. {
  140.    syssnd_stopsound (music_snd);
  141.    music_snd = NULL;
  142. }
  143.  
  144.  
  145. /*
  146.  * Main loop
  147.  */
  148. void game_run (void)
  149. {
  150.    U32 tm, tmx;
  151.  
  152.    loaddata (); /* load cached data */
  153.  
  154.    tm = sys_gettime ();
  155.    game_state = INIT_GAME;
  156.  
  157.    /* main loop */
  158.    while (game_state != EXIT)
  159.    {
  160.       // Pierre-Marie Baty -- intro runs faster
  161.       if (game_state != INTRO_MAP)
  162.          game_period = (sysarg_args_period ? sysarg_args_period : GAME_PERIOD);
  163.       else
  164.          game_period = (sysarg_args_period ? sysarg_args_period : GAME_PERIOD) / 2;
  165.  
  166.       /* timer */
  167.       tmx = tm; tm = sys_gettime (); tmx = tm - tmx;
  168.       if (tmx < game_period)
  169.          sys_sleep (game_period - tmx);
  170.  
  171.       /* video */
  172.       /*DEBUG*//*game_rects=&draw_SCREENRECT;*//*DEBUG*/
  173.       sysvid_paint (game_rects);
  174.       draw_STATUSRECT.next = NULL; /* FIXME freerects should handle this */
  175.  
  176.       /* sound */
  177.       /*snd_mix ();*/
  178.  
  179.       /* events */
  180.       if (game_waitevt)
  181.          sysevt_wait (); /* wait for an event */
  182.       else
  183.          sysevt_poll (); /* process events (non-blocking) */
  184.  
  185.       /* frame */
  186.       frame ();
  187.    }
  188.  
  189.    freedata (); /* free cached data */
  190. }
  191.  
  192.  
  193. // Pierre-Marie Baty -- addition: sort high scores
  194. int sort_hscores (const void *q1, const void *q2)
  195. {
  196.    return (((hscore_t *) q1)->score < ((hscore_t *) q2)->score);
  197. }
  198.  
  199.  
  200. /*
  201.  * Prepare frame
  202.  *
  203.  * This function loops forever: use 'return' when a frame is ready.
  204.  * When returning, game_rects must contain every parts of the buffer
  205.  * that have been modified.
  206.  */
  207. static void frame (void)
  208. {
  209.    while (1)
  210.    {
  211.  
  212.       switch (game_state)
  213.       {
  214.  
  215.  
  216.       case INIT_GAME:
  217.          init ();
  218.          game_state = INTRO_MAIN;
  219.          break;
  220.  
  221.  
  222.  
  223.       case INTRO_MAIN:
  224.          switch (screen_introMain ())
  225.          {
  226.          case SCREEN_RUNNING:
  227.             return;
  228.          case SCREEN_DONE:
  229.             game_state = INTRO_MAP;
  230.             break;
  231.          case SCREEN_EXIT:
  232.             game_state = EXIT;
  233.             return;
  234.          }
  235.       break;
  236.  
  237.  
  238.  
  239.       case INTRO_MAP:
  240.          switch (screen_introMap ())
  241.          {
  242.          case SCREEN_RUNNING:
  243.             return;
  244.          case SCREEN_DONE:
  245.             game_waitevt = FALSE;
  246.             game_state = INIT_BUFFER;
  247.             break;
  248.          case SCREEN_EXIT:
  249.             game_state = EXIT;
  250.             return;
  251.          }
  252.       break;
  253.  
  254.  
  255.  
  256.       case INIT_BUFFER:
  257.          syssnd_pause (FALSE, TRUE); // reset sounds
  258.          sysvid_clear (); /* clear buffer */
  259.          draw_map (); /* draw the map onto the buffer */
  260.          draw_drawStatus (); /* draw the status bar onto the buffer */
  261.          game_rects = &draw_SCREENRECT; /* request full buffer refresh */
  262.          game_state = PLAY0;
  263.          return;
  264.  
  265.  
  266.  
  267.       case PAUSE_PRESSED1:
  268.          screen_pause (TRUE);
  269.          game_state = PAUSE_PRESSED1B;
  270.          break;
  271.  
  272.  
  273.  
  274.       case PAUSE_PRESSED1B:
  275.          if (control_status & CONTROL_PAUSE)
  276.             return;
  277.          game_state = PAUSED;
  278.          break;
  279.  
  280.  
  281.  
  282.       case PAUSED:
  283.          if (control_status & CONTROL_PAUSE)
  284.             game_state = PAUSE_PRESSED2;
  285.          if (control_status & CONTROL_EXIT)
  286.             game_state = EXIT;
  287.          return;
  288.  
  289.  
  290.  
  291.       case PAUSE_PRESSED2:
  292.          if (!(control_status & CONTROL_PAUSE))
  293.          {
  294.             game_waitevt = FALSE;
  295.             screen_pause (FALSE);
  296.             syssnd_pause (FALSE, FALSE);
  297.             game_state = PLAY2;
  298.          }
  299.       return;
  300.  
  301.  
  302.  
  303.       case PLAY0:
  304.          play0 ();
  305.          break;
  306.  
  307.  
  308.  
  309.       case PLAY1:
  310.          if (control_status & CONTROL_PAUSE)
  311.          {
  312.             syssnd_pause (TRUE, FALSE);
  313.             game_waitevt = TRUE;
  314.             game_state = PAUSE_PRESSED1;
  315.          }
  316.          else
  317.             game_state = PLAY2;
  318.          break;
  319.  
  320.  
  321.  
  322.       case PLAY2:
  323.          if (E_RICK_STTST (E_RICK_STDEAD))
  324.          {
  325.             /* rick is dead */
  326.             if (--game_lives)
  327.             {
  328.                game_state = RESTART;
  329.                if (want_infinitelives)
  330.                   game_lives = 6;
  331.             }
  332.             else
  333.                game_state = GAMEOVER;
  334.          }
  335.          else if (game_chsm)
  336.             game_state = CHAIN_SUBMAP; // request to chain to next submap
  337.          else
  338.             game_state = PLAY3;
  339.          break;
  340.  
  341.  
  342.  
  343.       case PLAY3:
  344.          play3 ();
  345.          return;
  346.  
  347.  
  348.  
  349.       case CHAIN_SUBMAP:
  350.          if (map_chain ())
  351.             game_state = CHAIN_END;
  352.          else
  353.          {
  354.             game_bullets = 0x06;
  355.             game_bombs = 0x06;
  356.             game_map++;
  357.  
  358.             if (game_map == 0x04)
  359.             {
  360.                /* reached end of game */
  361.                /* FIXME @292?*/
  362.             }
  363.  
  364.             game_state = CHAIN_MAP;
  365.          }
  366.          break;
  367.  
  368.  
  369.  
  370.       case CHAIN_MAP:/* CHAIN MAP */
  371.          switch (screen_introMap ())
  372.          {
  373.          case SCREEN_RUNNING:
  374.             return;
  375.          case SCREEN_DONE:
  376.             if (game_map >= 0x04)
  377.             {
  378.                /* reached end of game */
  379.                sysarg_args_map = 0;
  380.                sysarg_args_submap = 0;
  381.                sysarg_args_score = 0;
  382.                game_state = GAMEOVER;
  383.             }
  384.             else
  385.             {
  386.                /* initialize game */
  387.                ent_ents[1].x = map_maps[game_map].x;
  388.                ent_ents[1].y = map_maps[game_map].y;
  389.                map_frow = (U8) map_maps[game_map].row;
  390.                game_submap = map_maps[game_map].submap;
  391.                game_state = CHAIN_END;
  392.             }
  393.             break;
  394.                case SCREEN_EXIT:
  395.             game_state = EXIT;
  396.             return;
  397.          }
  398.          break;
  399.  
  400.  
  401.  
  402.       case CHAIN_END:
  403.          syssnd_pause (FALSE, TRUE); // reset sounds
  404.          map_init (); /* initialize the map */
  405.          isave (); /* save data in case of a restart */
  406.          ent_clprev (); /* cleanup entities */
  407.          draw_map (); /* draw the map onto the buffer */
  408.          draw_drawStatus (); /* draw the status bar onto the buffer */
  409.          draw_STATUSRECT.next = NULL;
  410.          game_state = PLAY3;
  411.          frame (); // Pierre-Marie Baty -- patch by Jason Andersen: skip refreshing the screen for 2 pumps
  412.          frame (); // to allow the sprites to settle into the appropriate frame
  413.          game_rects = &draw_SCREENRECT;  /* request full screen refresh */
  414.          return;
  415.  
  416.  
  417.  
  418.       case SCROLL_UP:
  419.          switch (scroll_up ())
  420.          {
  421.          case SCROLL_RUNNING:
  422.             return;
  423.          case SCROLL_DONE:
  424.             game_state = PLAY0;
  425.             break;
  426.          }
  427.          break;
  428.  
  429.  
  430.  
  431.       case SCROLL_DOWN:
  432.          switch (scroll_down ())
  433.          {
  434.          case SCROLL_RUNNING:
  435.             return;
  436.          case SCROLL_DONE:
  437.             game_state = PLAY0;
  438.             break;
  439.          }
  440.          break;
  441.  
  442.  
  443.  
  444.       case RESTART:
  445.          restart ();
  446.          game_state = PLAY0;
  447.          return;
  448.  
  449.  
  450.  
  451.       case GAMEOVER:
  452.          switch (screen_gameover ())
  453.          {
  454.          case SCREEN_RUNNING:
  455.             return;
  456.          case SCREEN_DONE:
  457.             game_state = GETNAME;
  458.             break;
  459.          case SCREEN_EXIT:
  460.             game_state = EXIT;
  461.             break;
  462.          }
  463.          break;
  464.  
  465.  
  466.  
  467.       case GETNAME:
  468.          switch (screen_getname ()) {
  469.          case SCREEN_RUNNING:
  470.             return;
  471.          case SCREEN_DONE:
  472.             game_state = INIT_GAME;
  473.             return;
  474.          case SCREEN_EXIT:
  475.             game_state = EXIT;
  476.             break;
  477.          }
  478.          break;
  479.  
  480.  
  481.  
  482.       case EXIT:
  483.          return;
  484.  
  485.       }
  486.    }
  487. }
  488.  
  489.  
  490. /*
  491.  * Initialize the game
  492.  */
  493. static void init (void)
  494. {
  495.    U8 i;
  496.  
  497.    E_RICK_STRST (0xff);
  498.  
  499.    game_lives = 6;
  500.    game_bombs = 6;
  501.    game_bullets = 6;
  502.    game_score = 0;
  503.  
  504.    game_map = sysarg_args_map;
  505.  
  506.    if (sysarg_args_submap == 0)
  507.    {
  508.       game_submap = map_maps[game_map].submap;
  509.       map_frow = (U8)map_maps[game_map].row;
  510.    }
  511.    else
  512.    {
  513.       /* dirty hack to determine frow */
  514.       game_submap = sysarg_args_submap;
  515.       i = 0;
  516.       while (i < MAP_NBR_CONNECT && (map_connect[i].submap != game_submap || map_connect[i].dir != RIGHT))
  517.          i++;
  518.       map_frow = map_connect[i].rowin - 0x10;
  519.       ent_ents[1].y = 0x10 << 3;
  520.    }
  521.  
  522.    // Pierre-Marie Baty -- addition: start score
  523.    if (sysarg_args_score != 0)
  524.       game_score = sysarg_args_score + 1;
  525.  
  526.    ent_ents[1].x = map_maps[game_map].x;
  527.    ent_ents[1].y = map_maps[game_map].y;
  528.    ent_ents[1].w = 0x18;
  529.    ent_ents[1].h = 0x15;
  530.    ent_ents[1].n = 0x01;
  531.    ent_ents[1].sprite = 0x01;
  532.    ent_ents[1].front = FALSE;
  533.    ent_ents[ENT_ENTSNUM].n = 0xFF;
  534.  
  535.    map_resetMarks ();
  536.    map_init ();
  537.    isave ();
  538. }
  539.  
  540.  
  541. /*
  542.  * play0
  543.  *
  544.  */
  545. static void play0 (void)
  546. {
  547.    if (control_status & CONTROL_END)
  548.    {
  549.       /* request to end the game */
  550.       game_state = GAMEOVER;
  551.       return;
  552.    }
  553.  
  554.    if (control_last == CONTROL_EXIT)
  555.    {
  556.       /* request to exit the game */
  557.       game_state = EXIT;
  558.       return;
  559.    }
  560.  
  561.    ent_action (); /* run entities */
  562.    e_them_rndseed++; /* (0270) */
  563.  
  564.    game_state = PLAY1;
  565. }
  566.  
  567.  
  568. /*
  569.  * play3
  570.  *
  571.  */
  572. static void play3 (void)
  573. {
  574.    static rect_t *r;
  575.  
  576.    ent_draw (); /* draw all entities onto the buffer */
  577.  
  578.    /* sound */
  579.    draw_drawStatus (); /* draw the status bar onto the buffer*/
  580.  
  581.    r = &draw_STATUSRECT;
  582.    r->next = ent_rects; /* refresh status bar too */
  583.    game_rects = r; /* take care to cleanup draw_STATUSRECT->next later! */
  584.  
  585.    if (!E_RICK_STTST (E_RICK_STZOMBIE))
  586.    {
  587.       /* need to scroll ? */
  588.       if (ent_ents[1].y >= 200 + 4)
  589.       {
  590.          game_state = SCROLL_UP;
  591.          return;
  592.       }
  593.       if (ent_ents[1].y <= 100 - 4)
  594.       {
  595.          game_state = SCROLL_DOWN;
  596.          return;
  597.       }
  598.    }
  599.  
  600.    game_state = PLAY0;
  601. }
  602.  
  603.  
  604. /*
  605.  * restart
  606.  *
  607.  */
  608. static void restart (void)
  609. {
  610.    E_RICK_STRST (/*E_RICK_STDEAD | E_RICK_STZOMBIE*/ 0xff); // Pierre-Marie Baty -- correct sprite position on restart
  611.  
  612.    game_bullets = 6;
  613.    game_bombs = 6;
  614.  
  615.    ent_ents[1].n = 1;
  616.  
  617.    irestore ();
  618.    map_init ();
  619.    isave ();
  620.    ent_clprev ();
  621.    draw_map ();
  622.    draw_drawStatus ();
  623.    game_rects = &draw_SCREENRECT;
  624. }
  625.  
  626.  
  627. /*
  628.  * isave (0bbb)
  629.  *
  630.  */
  631. static void isave (void)
  632. {
  633.    e_rick_save ();
  634.    isave_frow = map_frow;
  635. }
  636.  
  637.  
  638. /*
  639.  * irestore (0bdc)
  640.  *
  641.  */
  642. static void irestore (void)
  643. {
  644.    e_rick_restore ();
  645.    map_frow = isave_frow;
  646. }
  647.  
  648.  
  649. /*
  650.  *
  651.  */
  652. static void loaddata (void)
  653. {
  654.    /*
  655.     * Cache sounds
  656.     *
  657.     * tune[0-5].wav not cached
  658.     */
  659.    WAV_GAMEOVER = syssnd_load ("sounds/gameover.wav");
  660.    WAV_SBONUS2 = syssnd_load ("sounds/sbonus2.wav");
  661.    WAV_BULLET = syssnd_load ("sounds/bullet.wav");
  662.    WAV_BOMBSHHT = syssnd_load ("sounds/bombshht.wav");
  663.    WAV_EXPLODE = syssnd_load ("sounds/explode.wav");
  664.    WAV_STICK = syssnd_load ("sounds/stick.wav");
  665.    WAV_WALK = syssnd_load ("sounds/walk.wav");
  666.    WAV_CRAWL = syssnd_load ("sounds/crawl.wav");
  667.    WAV_JUMP = syssnd_load ("sounds/jump.wav");
  668.    WAV_PAD = syssnd_load ("sounds/pad.wav");
  669.    WAV_BOX = syssnd_load ("sounds/box.wav");
  670.    WAV_BONUS = syssnd_load ("sounds/bonus.wav");
  671.    WAV_SBONUS1 = syssnd_load ("sounds/sbonus1.wav");
  672.    WAV_DIE = syssnd_load ("sounds/die.wav");
  673.    WAV_ENTITY[0] = syssnd_load ("sounds/ent0.wav");
  674.    WAV_ENTITY[1] = syssnd_load ("sounds/ent1.wav");
  675.    WAV_ENTITY[2] = syssnd_load ("sounds/ent2.wav");
  676.    WAV_ENTITY[3] = syssnd_load ("sounds/ent3.wav");
  677.    WAV_ENTITY[4] = syssnd_load ("sounds/ent4.wav");
  678.    WAV_ENTITY[5] = syssnd_load ("sounds/ent5.wav");
  679.    WAV_ENTITY[6] = syssnd_load ("sounds/ent6.wav");
  680.    WAV_ENTITY[7] = syssnd_load ("sounds/ent7.wav");
  681.    WAV_ENTITY[8] = syssnd_load ("sounds/ent8.wav");
  682.  
  683.    // Pierre-Marie Baty -- addition: load high scores
  684.    {
  685.       char hiscorefile_fullpathname[1024];
  686.       char linebuf[256];
  687.       hscore_t *hscore;
  688.       U32 score;
  689.       char *ptr;
  690.       size_t i, j;
  691.       FILE *fp;
  692.       sprintf_s (hiscorefile_fullpathname, 1024, "%s/%s", sys_getbasepath (), "hiscores.txt");
  693.       fopen_s (&fp, hiscorefile_fullpathname, "rb");
  694.       if (fp != NULL)
  695.       {
  696.          for (i = 0; (fgets (linebuf, sizeof (linebuf), fp) != NULL) && (i < 8);)
  697.          {
  698.             score = strtol (linebuf, &ptr, 10);
  699.             if (ptr <= linebuf)
  700.                continue;
  701.             while ((*ptr == '\t') || (*ptr == ' '))
  702.                ptr++;
  703.             hscore = &game_hscores[i++];
  704.             hscore->score = score;
  705.             for (j = 0; j < sizeof (hscore->name); j++)
  706.                hscore->name[j] = '@';
  707.             for (j = 0; j < sizeof (hscore->name); j++, ptr++)
  708.                hscore->name[j] = (((*ptr >= 'A') && (*ptr <= 'Z')) || (*ptr == '.') ? *ptr : '@');
  709.          }
  710.          qsort (game_hscores, 8, sizeof (hscore_t), sort_hscores);
  711.          fclose (fp);
  712.       }
  713.    }
  714. }
  715.  
  716.  
  717. /*
  718.  *
  719.  */
  720. static void freedata (void)
  721. {
  722.    syssnd_stopall ();
  723.    syssnd_free (WAV_GAMEOVER);
  724.    syssnd_free (WAV_SBONUS2);
  725.    syssnd_free (WAV_BULLET);
  726.    syssnd_free (WAV_BOMBSHHT);
  727.    syssnd_free (WAV_EXPLODE);
  728.    syssnd_free (WAV_STICK);
  729.    syssnd_free (WAV_WALK);
  730.    syssnd_free (WAV_CRAWL);
  731.    syssnd_free (WAV_JUMP);
  732.    syssnd_free (WAV_PAD);
  733.    syssnd_free (WAV_BOX);
  734.    syssnd_free (WAV_BONUS);
  735.    syssnd_free (WAV_SBONUS1);
  736.    syssnd_free (WAV_DIE);
  737.    syssnd_free (WAV_ENTITY[0]);
  738.    syssnd_free (WAV_ENTITY[1]);
  739.    syssnd_free (WAV_ENTITY[2]);
  740.    syssnd_free (WAV_ENTITY[3]);
  741.    syssnd_free (WAV_ENTITY[4]);
  742.    syssnd_free (WAV_ENTITY[5]);
  743.    syssnd_free (WAV_ENTITY[6]);
  744.    syssnd_free (WAV_ENTITY[7]);
  745.    syssnd_free (WAV_ENTITY[8]);
  746.  
  747.    // Pierre-Marie Baty -- addition: save high scores
  748.    {
  749.       char hiscorefile_fullpathname[1024];
  750.       size_t i, j;
  751.       FILE *fp;
  752.       sprintf_s (hiscorefile_fullpathname, 1024, "%s/%s", sys_getbasepath (), "hiscores.txt");
  753.       fopen_s (&fp, hiscorefile_fullpathname, "wb");
  754.       if (fp != NULL)
  755.       {
  756.          for (i = 0; i < sizeof (game_hscores) / sizeof (game_hscores[0]); i++)
  757.          {
  758.             fprintf (fp, "%d\t", game_hscores[i].score);
  759.             for (j = 0; j < sizeof (game_hscores[i].name); j++)
  760.                fputc ((game_hscores[i].name[j] != '@' ? game_hscores[i].name[j] : ' '), fp);
  761.             fputc ('\n', fp);
  762.          }
  763.          fclose (fp);
  764.       }
  765.    }
  766. }
  767.