Subversion Repositories Games.Rick Dangerous

Rev

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