/*
 *  Copyright (c) Ross Inglis 1994.
 *
 *  You have my permission to do what you like with this.
 *
 *  DISCLAIMER: This program implements my interpretation of the Olympia II
 *              combat rules. I do NOT warrant that they match Rich Skrenta's
 *              implementation of the same. This program aims to give you an
 *              idea of the likely outcome of a battle. It does not make any
 *              guarantees about what the real game outcome will actually be.
 *
 */

/* Uncomment for crude summary of random_int() results */

/* #define CHECK_RANDOM 1 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <memory.h>
#include <ctype.h>
#include <time.h>


/* The isatty() subroutine is in various places depending on the compiler
 * so select one of the following by predefining the appropriate tag. Other
 * compilers will put it in stdlib.h which has already been included.
 */

#ifdef MSC             /* Microsoft C vers 6.00a */
#include <io.h>
#endif

#ifdef GCC             /* Gnu C */
#include <unistd.h>
#endif


typedef unsigned int  BOOL;    /* TRUE or FALSE */
typedef unsigned int  UINT;    /* use only 0 - 0xFFFF */
typedef unsigned long LONG;    /* 0 - 0xFFFFFFFFL */

#define TRUE 1
#define FALSE 0


#define DEF_MAXIMUM_RUN_TIME    0L
#define DEF_MAXIMUM_BATTLE_TIME 0L
#define DEF_PROGRESS_INTERVAL   10L
#define DEF_MAXIMUM_LINE_LIMIT  0L

#define ENV_MAXIMUM_RUN_TIME    "FIGHT_RUNTIME"    /* Total time for all battles */
#define ENV_MAXIMUM_BATTLE_TIME "FIGHT_BATTLETIME" /* Time for one battle */
#define ENV_PROGRESS_INTERVAL   "FIGHT_INTERVAL"   /* Time between progress messages */
#define ENV_MAXIMUM_LINE_LIMIT  "FIGHT_MAXLINES"   /* Maximum output lines */


#define TYPE_CREATURE     0
#define TYPE_NOBLE        1
#define TYPE_ARCHER       2
#define TYPE_FORT         3
#define TYPE_SHIP         4
#define TYPE_SIEGE_ENGINE 5
#define TYPE_PLURAL       6

typedef struct _defn {
    struct _defn *next;
    char  *code;
    char  *name;
    UINT   type;
    UINT   attack;
    UINT   sea_attack;
    UINT   defense;
    UINT   sea_defense;
    UINT   missile;
    UINT   saving_roll;
} DEFN;

static DEFN *definition = NULL;

typedef struct _plural {
    struct _plural *next;
    UINT   length_in;
    char  *data_in;
    UINT   length_out;
    char  *data_out;
} PLURAL;

static PLURAL *plural_list = NULL;

typedef struct _member {
    struct _member *next;
    struct _member *stacked_under;
    struct _member *protects;
    DEFN  *what;
    UINT   number;
    UINT   start_number;
    UINT   attack;
    UINT   defense;
    UINT   missile;
    UINT   value;
    UINT   protection;
    UINT   start_protection;
    UINT   behind;
    BOOL   stack_leader;
    UINT   average_casualties;
    LONG   survivors[1];
} MEMBER;

typedef struct _side {
    MEMBER *list_first;
    MEMBER *list_last;
    LONG    victories;
    LONG    value;
    LONG    start_value;
    LONG    threshold;
} SIDE;

typedef struct _battle {
    SIDE side1;
    SIDE side2;
} BATTLE;

typedef struct _file_names {
    struct _file_names *next;
    UINT  length;
    char *name;
} FILE_NAMES;

typedef struct _partial_results {
    DEFN       *code;
    MEMBER     *stack;
    UINT        number;
    BOOL        expect_number;
    UINT        behind;
    BOOL        look_for_behind;
    BOOL        look_for_filename;
    BOOL        last_was_attacker_break;
    BOOL        last_was_defender_break;
    UINT        attacker_break;
    UINT        defender_break;
    UINT        attd_preset;
    UINT        dttd_preset;
    BOOL        raining;
    BOOL        windy;
    BOOL        verbose;
    BOOL        debug;
    LONG        num_of_battles;
    BOOL        expect_num_of_battles;
    LONG        run_time;
    BOOL        expect_run_time;
    FILE_NAMES *list;
} PARTIAL_RESULTS;

static BATTLE battle;
static BOOL   done_one;

#define RAND_SHUFFLE_SIZE 32   /* Should be a power of 2 */

static LONG random_seed;
static LONG random_shuffle[ RAND_SHUFFLE_SIZE ];

static LONG abs_maximum_run_time;
static LONG maximum_run_time;
static LONG progress_interval;
static LONG maximum_line_limit;
static LONG output_line_count;

static BOOL time_to_die = FALSE;

static char version[] = "Olympia II Battle Simulator  version 1.5\n";

static char *spec[] = {
"",
"",
"Usage:   fight attacker_spec * defender_spec [flags]",
"",
"'attacker_spec' and 'defender_spec' have the same format. They must be",
"separated by a '*' character. Each consists of a list of one or more",
"participating unit codes using abbreviations defined in the 'fight.dat'",
"file. Stack organisation may be specified using [] brackets.",
"",
"Examples:",
"",
"   fight n n n * r5                      3 standard nobles, not stacked",
"                                         together, attacks 5 rats.",
"",
"   fight n[s10 p8 wk5] n[pk2] * n[c10]   1 noble with 10 soldiers,",
"                                         8 peasants, and 5 workers, helped",
"                                         by a noble with 2 pikemen, attacks",
"                                         a noble with 10 crossbowmen.",
"",
"   fight n[s5 n3] * w7                   1 noble with 5 soldiers accompanied",
"                                         3 nobles in the same stack, attacks",
"                                         7 wolves.",
"",
"   fight n[eg3 n@2[ea5]] * ha3           1 noble with 3 elite guards",
"                                         accompanied by a noble who has set",
"                                         his behind state to 2 with 5 elite",
"                                         archers attacking 3 harpies.",
"",
"   fight n[s40] * tow[n[s15]]            1 noble with 40 soldiers attacking",
"                                         a noble with 15 soldiers inside a",
"                                         tower.",
"",
"Space characters are not significant except between unit codes.",
"",
"Some systems seem to do odd things with * and [ characters. If you are",
"getting unfathomable error messages try enclosing the whole of your",
"parameters in double or single quotes, eg:",
"",
"   fight 'n[s40] * tow[n[s15]]'",
"",
"Alternatively use    : or !   instead of   *",
"                     ( or {   instead of   [",
"                     ) or }   instead of   ]",
"",
"Consult the fight.dat file for a full list of unit codes. To use",
"non-standard nobles you will need to add them to the data file and",
"invent your own unit codes for them.",
"",
"'flags' are optional and can be positioned anywhere. If more than one flag",
"is used they can be joined together behind a single '-' but must not contain",
"spaces.",
"",
"  -a[<number>] Attacker will fight to the death or if optional <number> is",
"               supplied, until his value is reduced to <number>% of his",
"               starting value.",
"  -b[<number>] Both attacker and defender will fight to the death or if",
"               optional <number> is supplied, until his value is reduced",
"               to <number>% of his starting value.",
"  -d[<number>] Defender will fight to the death (default) or if optional",
"               <number> is supplied, until his value is reduced to <number>%",
"               of his starting value.",
"  -e           Neither attacker or defender fight to the death.",
"  -f<filename> Read parameters and battle definitions from <filename>. Lines",
"               in file beginning @ add new unit definitions just like lines",
"               in fight.dat. Blank lines or lines starting with # are ignored.",
"               All other lines are treated just like command line parameters",
"               and define individual battles. Normally each line will define a",
"               new battle except lines ending with \\ which are continued on",
"               the next line. Maximum line length is about 500 characters but",
"               there is no limit to the number of continuation lines other",
"               than available memory.",
"  -h           Display this help text.",
"  -n<number>   <number> specifies the number of battles to fight, the",
"               default is 5000 unless the -p switch is used in which case",
"               the default is 1. No space between n and <number>.",
"  -p           Print a blow by blow account of the battle(s).",
"  -r           Weather is raining.",
"  -t<number>   Limit battle run time to <number> seconds.",
"  -v           Verbose, give a more detailed casualty report.",
"  -w           Weather is windy.",
"",
"Example output:",
"",
"   Olympia II Battle Simulator  version 1.5",
"   Parameters: n[s20 cp] * tow[n[s10]] -v",
"   1506  74.50%  25.50%",
"   3132  74.58%  25.42%",
"   4752  74.10%  25.90%",
"   5000 battles fought in 31 seconds, 161.29 battles/s",
"",
"   ATTACKER (Break at 50%) :",
"",
"       1 noble (80,80,0) [1.00], with:      1x100%",
"         20 soldiers (5,5,0) [8.40]        20x0%     19x0%     18x1%     17x2%",
"                                           16x3%     15x5%     14x6%     13x10%",
"                                           12x7%     11x7%     10x7%      9x7%",
"                                            8x6%      7x5%      6x5%      5x4%",
"                                            4x4%      3x4%      2x3%      1x4%",
"                                            0x12%",
"         1 catapult (25,200,25) [0.73]      1x73%     0x27%",
"",
"       Average losses : 12 soldiers",
"",
"   DEFENDER (Fighting to the death) :",
"",
"       tower (100:20) [4,83], owner:       20x3%     19x3%     18x2%     17x2%",
"                                           16x1%     15x1%     14x1%     13x2%",
"                                           12x2%     11x2%     10x2%      9x2%",
"                                            8x2%      7x2%      6x2%      5x2%",
"                                            4x2%      3x2%      2x2%      1x1%",
"                                            0x64%",
"                                          100x40%    99x2%     98x2%     97x2%",
"                                           96x2%     95x1%     94x1%     93x1%",
"                                           92x2%     91x1%     90x2%     89x2%",
"                                           88x1%     87x1%     86x1%     85x2%",
"                                           84x1%     83x1%     82x1%     81x1%",
"                                           80x1%     79x1%     78x1%     77x1%",
"                                           76x1%     75x1%     74x1%     73x1%",
"                                           72x1%     71x1%     70x1%     69x1%",
"                                           68x1%     67x1%     66x1%     65x1%",
"                                           64x1%     63x1%     62x1%     60x1%",
"                                           59x1%     56x1%     53x1%     52x1%",
"                                           46x1%     45x0%      0x1%",
"         1 noble (80,80,0) [0.26], with:    1x26%     0x74%",
"           10 soldiers (5,5,0) [0.75]      10x0%      9x0%      8x1%      7x2%",
"                                            6x2%      5x2%      4x3%      3x2%",
"                                            2x3%      1x3%      0x82%",
"",
"       Average losses : 1 noble",
"                        9 soldiers",
"",
"   LIKELIHOOD OF WIN 73.92%",
"",
"Lines 3 and 4 are progress messages output every 10 seconds to show that",
"something is really happening. The two percentages are wins and losses for",
"the attacker.",
"",
"For each unit participating, a single line lists initial number, unit name,",
"combat ratings in round brackets, and average number of survivors in square",
"brackets. For fortifications or ships the combat ratings are men protected",
"and defense rating, and the values in square brackets are average remaining",
"defense rating and average remaining structure. With the verbose switch the",
"remaining part of the line details number of survivors x percentage of",
"battles where that number survived. This continues to subsequent lines if",
"it cannot fit on one line. For fortifications or ships this lists defense",
"rating remaining and then a second list shows structure remaining.",
"",
"An '@n' between combat ratings and survivers designates the behind state of",
"the unit where 'n' is some number greater than zero.",
"",
"This is freeware so please share it and enjoy.",
"",
"Copyright (c) Ross Inglis 1994" };

static void error (
    char *filename,
    UINT  line_number )
{
    if ( filename != NULL ) {
        printf( "At line %u in file '%s'\n", line_number, filename );
    }
    printf( "Use -h switch to get help text\n" );
    exit( 1 );
}

static void print_spec ( void )
{
    UINT i;

    signal( SIGINT, SIG_DFL );

    for ( i = 0; i < sizeof( spec )/ sizeof( spec[0]); i ++ ) {

        printf( "%s\n", spec[ i ] );
    }
}

static int print_fraction (
    LONG numerator,
    LONG denominator,
    UINT precision )
{
    LONG remainder;
    UINT l, p;
    LONG whole_part;
    char decimals[ 10 ];

    if ( precision <= 0 ) {

        l = printf( "%lu", ( numerator + denominator/2 ) / denominator );
    }
    else {

        p = 0;
        remainder = numerator % denominator;
        whole_part = numerator / denominator;

        while ( p < precision && p < 10 ) {

            remainder *= 10;
            decimals[ p ++ ] = (char)((int)( remainder / denominator ) + '0' );
            remainder %= denominator;
        }

        /* Round to nearest */

        if ( denominator <= ( remainder + remainder )) {

            for (;;) {

                if ( p == 0 ) {

                    whole_part ++;
                    break;
                }

                if ( decimals[ --p ] < '9' ) {

                    decimals[ p ] ++;
                    break;
                }

                decimals[ p ] = '0';
            }
        }

        /* Then print it */

        l = printf( "%lu.", whole_part );

        for ( p = 0; p < precision && p < 10; p ++ ) {

            putchar( decimals[ p ]);
        }

        l += p;
    }

    return l;
}

/* The following random number generator is derived from the algorithms    */
/* described in: Numerical Recipes in C, The Art of Scientific Computing,  */
/* 2nd edition, by WH Press, SA Teukolsky, WT Vetterling and BP Flannery,  */
/* published by Cambridge University Press 1992. See chapter 7.1, partic-  */
/* ularly pages 280 and 284.                                               */

#define RAW_RANDOM (( random_seed = ( random_seed * 1664525 + 1013904223 )) & 0xFFFFFFFFl )

LONG random_long ( void )
{
    int  n;
    LONG x, v;

    x = RAW_RANDOM;

    /* This is an intelligence test for your compiler. It is supposed to   */
    /* realise that no one cares about saving 'n' and that the division by */
    /* 'sizeof(long)' can be omitted because its the scaling factor when   */
    /* 'n' is used to index the 'shuffle' array. This is also a slight     */
    /* deviation from the reference which recommends 'n' be calculated     */
    /* from the most significant end of 'x' by division.                   */

    n = ((int)x & ((RAND_SHUFFLE_SIZE-1)*sizeof(LONG))) / sizeof(LONG);

    v = random_shuffle[ n ];
    random_shuffle[ n ] = x;

    return v;
}

static LONG verify_data[] = {
    0x3c6ef35fL,
    0x47502932L,
    0xd1ccf6e9L,
    0xaaf95334L,
    0x6252e503L,
    0x9f2ec686L,
    0x57fe6c2dL,
    0xa3d95fa8L,
    0x81fdbee7L,
    0x94f0af1aL,
    0xcbf633b1L
};

static void initialise_random_seed (
    void )
{
    int        n;
    time_t     r_time;
    struct tm *c_time;

    time( &r_time );
    c_time = gmtime( &r_time );

    /* Check the current implementation to see if it can produces the */
    /* correct sequence of random numbers.                            */

    random_seed = 0;
    n = 0;
    while ( n < ( sizeof( verify_data ) / sizeof( verify_data[0] )) &&
            verify_data[ n ] == RAW_RANDOM ) {

        n ++;
    }

    if ( n < ( sizeof( verify_data ) / sizeof( verify_data[0] ))) {

        printf( "Fatal error! Random number generator is not functioning correctly\n" );
        exit(2);
    }

    /* Now initialise to a unique value to make all runs different. */

    random_seed = c_time->tm_sec  * 355111 +
                  c_time->tm_min  * 173209 +
                  c_time->tm_hour * 85661 +
                  c_time->tm_mday * 39499 +
                  c_time->tm_mon  * 19543 +
                  c_time->tm_year * 9887;

    /* Cycle through enough random numbers to guarantee that the shuffle  */
    /* table is full. This is prefered to filling the table sequentially  */
    /* with the first RAND_SHUFFLE_SIZE numbers as it ensures that future */
    /* calls on random() will have a guaranteed period of ULONG_MAX+1. A  */
    /* simple fill routine would cause a slight glitch in the first cycle */
    /* near its end.                                                      */

    for ( n = 0; n < ( RAND_SHUFFLE_SIZE * sizeof(LONG)); n ++ ) {

        random_long();
    }
}

#ifdef CHECK_RANDOM

typedef struct _tally {
    UINT min;
    UINT max;
    LONG *counts;
} TALLY;

static TALLY table[ 501 ];

static void clear_table ( void )
{
    int i;

    for ( i = 1; i <= 500; i ++ ) {

        table[ i ].min = 0xFFFF;
        table[ i ].max = 0;
        table[ i ].counts = malloc( sizeof( LONG ) * i );
        memset( table[ i ].counts, 0, sizeof( LONG ) * i );
    }
}

static void print_table ( void )
{
    int i, j;
    LONG ct, average, min_ct, max_ct;

    for ( i = 1; i <= 500; i ++ ) {

        ct = 0L;
        average = 0L;
        min_ct = 0xFFFFFFFFL;
        max_ct = 0L;
        for ( j = 0; j < i; j ++ ) {

            if ( min_ct > table[ i ].counts[ j ] ) {

                min_ct = table[ i ].counts[ j ];
            }

            if ( max_ct < table[ i ].counts[ j ] ) {

                max_ct = table[ i ].counts[ j ];
            }

            ct += table[ i ].counts[ j ];
            average += table[ i ].counts[ j ] * j;
        }

        if ( ct > 0L ) {

            printf( "#%u, occurs %lu, aver ", i, ct );
            print_fraction( average, ct, 2 );
            printf( ", min %u", table[ i ].min );
            printf( ", max %u", table[ i ].max );
            printf( ", aver ct " );
            print_fraction( ct, (LONG)i, 2 );
            printf( ", min ct %lu (", min_ct );
            print_fraction( min_ct * i, ct, 2 );
            printf( "), max ct %lu (", max_ct );
            print_fraction( max_ct * i, ct, 2 );
            printf( ")\n" );
        }

        table[ i ].min = 0xFFFF;
        table[ i ].max = 0;

        memset( table[ i ].counts, 0, sizeof( LONG ) * i );
    }
}

#endif


static UINT random_int (
    UINT x )
{
#ifndef CHECK_RANDOM

    return (UINT)((( random_long() >> 16 ) * x ) >> 16 );

#else

    UINT value;

    value = (UINT)((( random_long() >> 16 ) * x ) >> 16 );

    if ( 0 < x && x <= 500 ) {
        if ( table[x].min > value ) {
            table[x].min = value;
        }
        if ( table[x].max < value ) {
            table[x].max = value;
        }
        table[x].counts[value] ++;
    }

    return value;

#endif
}


static void kill_handler ( int sig )
{
    if ( sig == sig ) {

        /* Then avoid warnings for ignoring my parameters! */
    }

    if ( time_to_die != TRUE ) {

        time_to_die = TRUE;

        signal( SIGINT, SIG_DFL );
    }
}


static void print_line (
    void )
{
    printf( "\n" );

    if ( maximum_line_limit > 0 ) {

        output_line_count ++;
        if ( output_line_count > maximum_line_limit ) {

            printf( "RUN ABANDONED: Maximum output line count exceeded\n" );
            exit( 1 );
        }
    }
}

static DEFN *find_code (
    char *code,
    UINT  code_len,
    char *filename,
    UINT  line_number )
{
    DEFN   *scan;

    scan = definition;
    while ( scan != NULL &&
           ( strlen( scan->code ) != code_len ||
             memcmp( scan->code, code, code_len ) != 0 )) {
        scan = scan->next;
    }

    if ( scan == NULL ) {
        printf( "\nUnrecognised code: %s\n", code );
        error( filename, line_number );
    }

    return scan;
}

static MEMBER *add_member (
    SIDE   *side,
    DEFN   *code,
    UINT    number,
    UINT    behind,
    BOOL    stack_leader,
    MEMBER *stack,
    char   *filename,
    UINT    line_number )
{
    LONG    value;
    MEMBER *member;
    MEMBER *leader;

    if ( code->type == TYPE_FORT && side->list_first != NULL ) {

        printf( "\nFortifications must be first unit: %s\n", code->code );
        error( filename, line_number );
    }

    if ( code->type == TYPE_SHIP && stack != NULL ) {

        printf( "\nShips may not be stacked under other units: %s\n", code->code );
        error( filename, line_number );
    }

    if (( code->type == TYPE_SHIP || code->type == TYPE_FORT ) && number > 1 ) {

        printf( "\nNumber may not follow ship or fortification specifier: %s\n", code->code );
        error( filename, line_number );
    }

    if (( code->type == TYPE_SHIP || code->type == TYPE_FORT ) && behind > 0 ) {

        printf( "\nA ship or fortification cannot be behind: %s\n", code->code );
        error( filename, line_number );
    }

    if (( code->type == TYPE_SHIP || code->type == TYPE_FORT ) && stack_leader == FALSE ) {

        printf( "\nA ship or fortification needs someone inside: %s\n", code->code );
        error( filename, line_number );
    }

    if ( code->type == TYPE_SHIP || code->type == TYPE_FORT ) {

        member = (MEMBER *)malloc( sizeof( *member ) + ( code->defense + 101 ) * sizeof( member->survivors[0]));
    }
    else {

        member = (MEMBER *)malloc( sizeof( *member ) + number * sizeof( member->survivors[0]));
    }

    if ( stack_leader == TRUE && number != 1 ) {

        printf( "\nOnly a single stack leader is allowed : %s\n", code->code );
        error( filename, line_number );
    }

    member->next = NULL;
    member->stacked_under = stack;
    member->what = code;

    if ( code->type == TYPE_SHIP || code->type == TYPE_FORT ) {

        member->number = 100;
    }
    else {

        member->number = number;
    }

    member->start_number = member->number;

    if ( behind == 0 &&
         stack != NULL &&
         stack_leader == FALSE &&
         code->type != TYPE_NOBLE ) {

        member->behind = stack->behind;
    }
    else {

        member->behind = behind;
    }

    member->stack_leader = stack_leader;
    member->protection = 0;
    member->start_protection = 0;

    member->average_casualties = 0;
    member->missile = code->missile;

    if ( side->list_first != NULL &&
         side->list_first->what->type == TYPE_SHIP ) {

        member->attack  = code->sea_attack;
        member->defense = code->sea_defense;
    }
    else {

        member->attack  = code->attack;
        member->defense = code->defense;
    }

    if ( code->type == TYPE_SHIP || code->type == TYPE_FORT ) {

        member->value = 0;
    }
    else {

        if ( member->missile < member->attack ) {

            member->value = member->defense + member->attack;
        }
        else {

            member->value = member->defense + member->missile;
        }

        value = (LONG)( member->value * member->number );

        side->value += value;
        side->start_value += value;
        side->threshold += value;
    }

    if ( code->type == TYPE_NOBLE || stack == NULL ) {

        leader = side->list_first;
        while ( leader != NULL &&
               ( leader->what->type == TYPE_FORT ||
                 leader->what->type == TYPE_SHIP )) {

            leader = leader->next;
        }
    }
    else {

        if ( stack->what->type == TYPE_FORT ||
             stack->what->type == TYPE_SHIP ) {

            leader = NULL;
        }
        else {

            leader = stack;
        }
    }

    member->protects = leader;
    if ( leader != NULL ) {

        leader->protection ++;
        leader->start_protection ++;
    }

    if ( side->list_last != NULL ) {
        side->list_last->next = member;
    }
    else {
        side->list_first = member;
    }

    side->list_last = member;

    if ( stack_leader == TRUE ) {
        return member;
    }

    return stack;
}


static void apply_wind_and_rain (
    MEMBER *list,
    BOOL    raining,
    BOOL    windy )
{
    /* Note that when I reduce the missile rating I do not reduce the
     * units value. I do not know if this is correct!
     */

    while ( list != NULL ) {

        if ( raining == TRUE && list->what->type == TYPE_ARCHER ) {

            list->missile = 0;
        }
        else if ( windy == TRUE ) {

            list->missile /= 2;
        }

        list = list->next;
    }
}


static void eligible_members (
    MEMBER *list,
    MEMBER *other,
    UINT   *number_attack,
    UINT   *number_defend,
    UINT   *behind )
{
    UINT    normal_number;
    UINT    missile_number;
    UINT    defend_number;
    UINT    extra_defend_number;
    UINT    current_behind;
    UINT    next_behind;
    MEMBER *member;

    current_behind = *behind;

    do {

        member = list;
        next_behind = 0xFFFF;
        missile_number = 0;
        defend_number = 0;
        extra_defend_number = 0;
        normal_number = 0;

        while ( member != NULL ) {

            if ( member->what->type == TYPE_FORT ||
                 member->what->type == TYPE_SHIP ) {

                /* For a structure, the number field represents
                 * remaining damage it can take. When this reaches
                 * zero it has been destroyed. Note that if the
                 * structure is the only defender then we need to
                 * advance the behind rank until we find a real
                 * defender.
                 */

                if ( member->number > 0 ) {

                    extra_defend_number ++;
                }
            }
            else if ( member->number > 0 ) {

                if ( member->what->type == TYPE_SIEGE_ENGINE &&
                    ( other->what->type != TYPE_FORT ||
                      other->number == 0 )) {

                    /* Seige engines only defend without attacking when there
                     * are no fortifications on the other side. Currently
                     * they can be the sole defender with a rank of archers
                     * behind them. This is one of the things that I don't
                     * know what Rich does. To advance the archers you need
                     * to change defend_number to extra_defend_number.
                     */

                    if ( member->behind <= current_behind ) {

                        if ( member->protection == 0 ) {

                            defend_number += member->number;
                        }
                    }
                    else if ( member->behind < next_behind ) {

                        next_behind = member->behind;
                    }
                }
                else if ( member->behind <= current_behind ) {

                    normal_number += member->number;
                    if ( member->protection == 0 ) {

                        defend_number += member->number;
                    }
                }
                else {

                    if ( member->behind < next_behind ) {

                        next_behind = member->behind;
                    }

                    if ( member->missile > 0 ) {

                        missile_number += member->number;
                    }
                }
            }

            member = member->next;
        }

        /* There must be someone available to defend the front rank */
        /* so advance it if there are no defenders so far found.    */

        if ( defend_number == 0 ) {

            current_behind = next_behind;
        }
    }
    while ( defend_number == 0 );

    *number_attack = normal_number + missile_number;
    *number_defend = defend_number + extra_defend_number;

    *behind = current_behind;
}


static void reset_totals (
    SIDE *side )
{
    MEMBER *list;

    side->victories = 0;

    list = side->list_first;
    while ( list != NULL ) {

        if ( list->what->type != TYPE_FORT &&
             list->what->type != TYPE_SHIP ) {

            memset( (void *)list->survivors,
                    0,
                    sizeof(list->survivors[0]) * ( list->start_number + 1 ));
        }
        else {

            memset( (void *)list->survivors,
                    0,
                    sizeof(list->survivors[0]) * ( list->what->defense + 102 ));
        }

        list = list->next;
    }
}


static void reset_for_battle (
    SIDE *side )
{
    MEMBER *list;

    side->value = side->start_value;

    list = side->list_first;
    while ( list != NULL ) {

        if ( list->what->type == TYPE_SHIP || list->what->type == TYPE_FORT ) {

            list->defense = list->what->defense;
        }

        list->number     = list->start_number;
        list->protection = list->start_protection;

        list = list->next;
    }
}


static void do_battle (
    LONG num_of_battles,
    LONG caller_run_time,
    BOOL debug )
{
    LONG    cycle;
    UINT    i, loser;
    UINT    aggressor_num_att;
    UINT    aggressor_num_def;
    UINT    aggressor_behind;
    UINT    victim_num_att;
    UINT    victim_num_def;
    UINT    victim_behind;
    UINT    attacker_behind;
    UINT    attacker_number;
    UINT    attacker_index;
    UINT    defender_behind;
    UINT    defender_number;
    UINT    defender_index;
    UINT    bonus;
    MEMBER *attackers, *sel_attacker;
    MEMBER *defenders, *sel_defender;
    UINT    random_value;
    UINT    numerator, denominator;
    time_t  begin_time, last_time, end_time;
    LONG    time_diff;
    BOOL    redo_aggressor, redo_victim;
    BOOL    console_output;

    if ( isatty( fileno( stdout ))) {
        console_output = TRUE;
    }
    else {
        console_output = FALSE;
    }

    reset_totals( &battle.side1 );
    reset_totals( &battle.side2 );

    if ( maximum_run_time == 0 ||
        ( abs_maximum_run_time != 0 &&
          abs_maximum_run_time < maximum_run_time )) {

        maximum_run_time = abs_maximum_run_time;
    }

    if ( maximum_run_time == 0 ||
        ( caller_run_time != 0 &&
          caller_run_time < maximum_run_time )) {

        maximum_run_time = caller_run_time;
    }

    loser = 0; /* To get rid of a warning */

    time( &begin_time );

    last_time = begin_time;

    for ( cycle = 0L; cycle < num_of_battles; cycle ++ ) {

        if ( debug == TRUE && time_to_die == FALSE ) {

            print_line();
            printf( "Battle #%lu", cycle + 1 );
            print_line();
            print_line();
        }

        reset_for_battle( &battle.side1 );
        reset_for_battle( &battle.side2 );

        aggressor_behind = 0;
        victim_behind = 0;

        redo_aggressor = TRUE;
        redo_victim = TRUE;

        while ( battle.side1.value > battle.side1.threshold &&
                battle.side2.value > battle.side2.threshold ) {

            /* Count number of eligible attackers on both sides.
             */

            if ( redo_aggressor == TRUE ) {

                eligible_members( battle.side1.list_first,
                                  battle.side2.list_first,
                                  &aggressor_num_att,
                                  &aggressor_num_def,
                                  &aggressor_behind );

                redo_aggressor = FALSE;
            }

            if ( redo_victim == TRUE ) {

                eligible_members( battle.side2.list_first,
                                  battle.side1.list_first,
                                  &victim_num_att,
                                  &victim_num_def,
                                  &victim_behind );

                redo_victim = FALSE;
            }

            /* Select a random attacker */

            attacker_index = random_int( aggressor_num_att + victim_num_att ) + 1;

            if ( attacker_index <= aggressor_num_att ) {

                loser = 2;

                attacker_behind = aggressor_behind;
                attacker_number = aggressor_num_att;

                defender_behind = victim_behind;
                defender_number = victim_num_def;

                attackers = battle.side1.list_first;
                defenders = battle.side2.list_first;
            }
            else {

                attacker_index -= aggressor_num_att;

                loser = 1;

                attacker_behind = victim_behind;
                attacker_number = victim_num_att;

                defender_behind = aggressor_behind;
                defender_number = aggressor_num_def;

                attackers = battle.side2.list_first;
                defenders = battle.side1.list_first;
            }

            /* Find the actual attacker */

            i = attacker_index;
            sel_attacker = attackers;
            for (;;) {

                if ( sel_attacker->what->type != TYPE_FORT &&
                     sel_attacker->what->type != TYPE_SHIP &&
                     sel_attacker->number > 0 &&
                    ( sel_attacker->what->type != TYPE_SIEGE_ENGINE ||
                     ( defenders->what->type == TYPE_FORT &&
                       defenders->number > 0 ))) {

                    if ( sel_attacker->behind <= attacker_behind ||
                         sel_attacker->missile > 0 ) {

                        if ( i <= sel_attacker->number ) {

                            break;
                        }

                        i -= sel_attacker->number;
                    }
                }

                sel_attacker = sel_attacker->next;
                if ( sel_attacker == NULL ) {

                    printf( "Disaster! NULL pointer in attacker selection loop\n" );
                    exit( 2 );
                }
            }

            /* Now find the defender */

            if ( sel_attacker->what->type == TYPE_SIEGE_ENGINE ) {

                /* If the attacker is a seige engine then the defender
                 * must be a fortification and it must be the first
                 * entry in the list, always select it as the defender.
                 */

                bonus = 0;
                sel_defender = defenders;
                defender_index = 1;
            }
            else {

                /* Pick a random victim */

                defender_index = random_int( defender_number ) + 1;

                i = defender_index;
                bonus = 0;
                sel_defender = defenders;
                for (;;) {

                    if ( sel_defender->what->type == TYPE_FORT ||
                         sel_defender->what->type == TYPE_SHIP ) {

                        bonus = 0;

                        if ( sel_defender->number > 0 ) {

                            if ( i <= 1 ) {

                                break;
                            }

                            i --;

                            /* The number of victim protected by the fort
                             * is stored in the 'attack' field. The current
                             * defense bonus is in the defense field.
                             */

                            if ( i <= sel_defender->attack ) {

                                bonus = sel_defender->defense;
                            }
                        }
                    }
                    else if ( sel_defender->number > 0 &&
                              sel_defender->behind <= defender_behind &&
                              sel_defender->protection == 0 ) {

                        if ( i <= sel_defender->number ) {

                            break;
                        }

                        i -= sel_defender->number;
                    }

                    sel_defender = sel_defender->next;
                    if ( sel_defender == NULL ) {

                        printf( "Disaster! NULL pointer in defender selection loop\n" );
                        exit( 2 );
                    }

                    /* If the next victim is not stacked under anyone then he
                     * cannot possibly be inside a fortification so cancel any
                     * defense bonus that may be outstanding.
                     */

                    if ( sel_defender->stacked_under == NULL ) {

                        bonus = 0;
                    }
                }
            }

            /* See if the attacker manages to kill/incapacitate the defender */

            if ( sel_attacker->missile < sel_attacker->attack &&
                 sel_attacker->behind <= attacker_behind ) {

                numerator = sel_attacker->attack;
            }
            else {

                numerator = sel_attacker->missile;
            }

            denominator = numerator + sel_defender->defense + bonus;

            random_value = random_int( denominator );

            if ( debug == TRUE && time_to_die == FALSE ) {

                printf( "S%u @%u : %s @%u (%u/%u) -> S%u @%u : %s @%u (%u/%u)",
                        3 - loser,
                        attacker_behind,
                        sel_attacker->what->name,
                        sel_attacker->behind,
                        attacker_index,
                        attacker_number,
                        loser,
                        defender_behind,
                        sel_defender->what->name,
                        sel_defender->behind,
                        defender_index,
                        defender_number );
                print_line();
                printf( "  bonus %u, chance ", bonus );
                print_fraction( (LONG)numerator, (LONG)denominator, 4 );
                printf( ", rolled " );
                print_fraction( (LONG)random_value, (LONG)denominator, 4 );
            }

            if ( random_value < numerator ) {

                if ( sel_defender->what->type != TYPE_FORT &&
                     sel_defender->what->type != TYPE_SHIP ) {

                    if ( sel_defender->what->saving_roll > 0 &&
                         random_int( 100 ) < sel_defender->what->saving_roll ) {

                        if ( debug == TRUE && time_to_die == FALSE ) {

                            printf( ", saves vs. death" );
                            print_line();
                        }
                    }
                    else {

                        sel_defender->number --;

                        if ( debug == TRUE && time_to_die == FALSE ) {

                            printf( ", dies, num now %u", sel_defender->number );
                            print_line();
                        }

                        if ( sel_defender->number > 0 ) {

                            if ( loser == 1 ) {
                                if ( sel_defender->what->type != TYPE_SIEGE_ENGINE ||
                                    ( attackers->what->type == TYPE_FORT &&
                                      attackers->number > 0 )) {

                                    aggressor_num_att --;
                                }
                                aggressor_num_def --;
                            }
                            else {

                                if ( sel_defender->what->type != TYPE_SIEGE_ENGINE ||
                                    ( attackers->what->type == TYPE_FORT &&
                                      attackers->number > 0 )) {

                                    victim_num_att --;
                                }
                                victim_num_def --;
                            }
                        }
                        else {

                            if ( loser == 1 ) {
                                redo_aggressor = TRUE;
                            }
                            else {
                                redo_victim = TRUE;
                            }

                            if ( sel_defender->protects != NULL ) {

                                sel_defender->protects->protection --;
                            }
                        }

                        if ( loser == 2 ) {
                            battle.side2.value -= sel_defender->value;
                        }
                        else {
                            battle.side1.value -= sel_defender->value;
                        }
                    }
                }
                else {

                    if ( sel_attacker->what->type == TYPE_SIEGE_ENGINE ) {

                        i = random_int( 6 ) + 5;
                    }
                    else {

                        i = 1;
                    }

                    if ( sel_defender->defense > 0 ) {

                        if ( sel_defender->defense > i ) {

                            sel_defender->defense -= i;
                        }
                        else {

                            sel_defender->defense = 0;
                        }

                        if ( debug == TRUE && time_to_die == FALSE ) {

                            printf( ", success, def now %u", sel_defender->defense );
                            print_line();
                        }
                    }
                    else {

                        if ( sel_defender->number > i ) {

                            sel_defender->number -= i;
                        }
                        else {

                            sel_defender->number = 0;

                            /* Defender no longer has a fortification and attacker
                             * can no longer use seige engines.
                             */

                            redo_aggressor = TRUE;
                            redo_victim = TRUE;
                        }

                        if ( debug == TRUE && time_to_die == FALSE ) {

                            printf( ", success, str now %u", sel_defender->number );
                            print_line();
                        }
                    }
                }
            }
            else if ( debug == TRUE && time_to_die == FALSE ) {

                printf( ", failed" );
                print_line();
            }
        }

        if ( loser == 1 ) {
            battle.side2.victories ++;
        }
        else {
            battle.side1.victories ++;
        }

        sel_attacker = battle.side1.list_first;
        sel_defender = battle.side2.list_first;

        do {

            while ( sel_attacker != NULL ) {

                if ( sel_attacker->what->type != TYPE_FORT &&
                     sel_attacker->what->type != TYPE_SHIP ) {

                    sel_attacker->survivors[ sel_attacker->start_number - sel_attacker->number ] ++;
                }
                else {

                    sel_attacker->survivors[ sel_attacker->what->defense - sel_attacker->defense ] ++;
                    sel_attacker->survivors[ sel_attacker->what->defense + 1 + sel_attacker->start_number - sel_attacker->number ] ++;
                }

                sel_attacker = sel_attacker->next;
            }

            sel_attacker = sel_defender;
            sel_defender = NULL;
        }
        while ( sel_attacker != NULL );

        time( &end_time );

        if ( maximum_run_time > 0 ) {
            time_diff = (LONG)( end_time - begin_time );
            if ( time_diff >= maximum_run_time ) {
                printf( "Run time limit exceeded" );
                print_line();
                break;
            }
        }

        if ( time_to_die == TRUE ) {

            printf( "Interrupt received" );
            print_line();
            break;
        }

        if ( debug == FALSE && console_output == TRUE ) {

            time_diff = (LONG)( end_time - last_time );
            if ( time_diff >= progress_interval ) {

                last_time = end_time;

                printf( "%lu  ", cycle + 1L );
                print_fraction( battle.side1.victories * 100L, battle.side1.victories + battle.side2.victories, 2 );
                printf( "%%" );
                print_line();
            }
        }
    }

    if ( debug == TRUE ) {
        print_line();
    }

    time_diff = (LONG)( end_time - begin_time );
    if ( abs_maximum_run_time > 0 ) {

        if ( time_diff < abs_maximum_run_time ) {

            abs_maximum_run_time -= time_diff;
        }
        else {

            /* All subsequent battles get 1 second to run in. We can't set */
            /* this to zero because that means run without a time limit!   */

            abs_maximum_run_time = 1L;
        }
    }

    printf( "%lu battle", cycle );
    if ( cycle != 1 ) {
        printf( "s" );
    }
    printf( " fought in %lu second", time_diff );
    if ( time_diff != 1L ) {
        printf( "s" );
    }
    if ( time_diff > 0 ) {
        printf( ", " );
        print_fraction( cycle, time_diff, 2 );
        printf( " battles/s" );
    }

    print_line();

#ifdef CHECK_RANDOM
    print_table();
#endif

    print_line();
}

static UINT pluralise (
    UINT  number,
    char  *name )
{
    UINT    l, result;
    PLURAL *scan;

    result = printf( "%u ", number );

    if ( number <= 1 ) {

        result += printf( "%s", name );
    }
    else {

        l = strlen( name );
        scan = plural_list;
        while ( scan != NULL &&
               ( scan->length_in > l ||
                 memcmp( scan->data_in, name + l - scan->length_in, scan->length_in ) != 0 )) {

            scan = scan->next;
        }

        if ( scan != NULL ) {

            if ( l > scan->length_in ) {

                result += printf( "%.*s%s", (int)( l - scan->length_in ), name, scan->data_out );
            }
            else {

                result += printf( "%s", scan->data_out );
            }
        }
        else {

            result += printf( "%ss", name );
        }
    }

    return result;
}

static void print_result (
    MEMBER *list,
    LONG    total,
    BOOL    verbose )
{
    int     i, l, n, w;
    UINT    j, k;
    MEMBER *stack;
    MEMBER *ptr;
    MEMBER *this;
    MEMBER *last;
    MEMBER *scan;
    UINT    casualties;
    LONG    average;

    i = 4;
    ptr = list;
    stack = NULL;
    while ( ptr != NULL ) {

        while ( stack != ptr->stacked_under ) {

            stack = stack->stacked_under;
            i -= 2;
        }

        l = printf( "%*s", i, " " );

        if ( ptr->what->type == TYPE_FORT || ptr->what->type == TYPE_SHIP ) {

            l += printf( "%s", ptr->what->name );
            l += printf( " (%u:%u) [", ptr->what->attack, ptr->what->defense );

            average = 0L;
            for ( j = 0; j <= ptr->what->defense; j ++ ) {

                average += (LONG) ( ptr->what->defense - j ) * ptr->survivors[ j ];
            }

            l += print_fraction( average, total, 0 );
            l += printf( "," );

            average = 0L;
            for ( j = 0; j <= ptr->start_number; j ++ ) {

                average += (LONG) ( ptr->start_number - j ) * ptr->survivors[ ptr->what->defense + j + 1 ];
            }

            l += print_fraction( average, total, 0 );
            l += printf( "]" );
        }
        else {

            l += pluralise( ptr->start_number, ptr->what->name );
            l += printf( " (%u,%u,%u)", ptr->attack, ptr->defense, ptr->missile );
            if ( ptr->behind > 0 ) {
                l += printf( " @%u", ptr->behind );
            }

            average = 0L;
            for ( j = 0; j <= ptr->start_number; j ++ ) {

                average += (LONG) ( ptr->start_number - j ) * ptr->survivors[ j ];
            }

            l += printf( " [" );
            l += print_fraction( average, total, 2 );
            l += printf( "]" );

            ptr->average_casualties = ptr->start_number - (UINT)(( average + ( total / 2 )) / total );
        }

        if ( ptr->stack_leader == TRUE ) {

            if ( ptr->what->type == TYPE_FORT || ptr->what->type == TYPE_SHIP ) {
                l += printf( ", owner:" );
            }
            else {
                l += printf( ", with:" );
            }

            stack = ptr;
            i += 2;
        }

        if ( verbose == TRUE ) {

            if ( l < 38 ) {

                l += printf( "%*s", 38 - l, " " );
            }

            w = l;
            n = 10;

            if ( ptr->what->type == TYPE_FORT || ptr->what->type == TYPE_SHIP ) {

                k = ptr->what->defense + 1;

                for ( j = 0; j <= ptr->what->defense; j ++ ) {

                    if ( ptr->survivors[ j ] > 0 ) {

                        if ( n > 10 ) {
                            n = 10;
                        }

                        if ((UINT)( l + 10 - n ) > 70 ) {
                            print_line();
                            printf( "%*s", w, " " );
                            l = w;
                        }
                        else if ( n < 10 ) {
                            l += printf( "%*s", 10 - n, " " );
                        }

                        n  = printf( " %3ux", ptr->what->defense - j );
                        n += print_fraction( ptr->survivors[ j ] * 100, total, 0 );
                        n += printf( "%%" );

                        l += n;
                    }
                }

                l = 80;
            }
            else {

                k = 0;
            }

            for ( j = 0; j <= ptr->start_number; j ++ ) {

                if ( ptr->survivors[ j + k ] > 0 ) {

                    if ( n > 10 ) {
                        n = 10;
                    }
                    if ((UINT)( l + 10 - n ) > 70 ) {
                        print_line();
                        printf( "%*s", w, " " );
                        l = w;
                    }
                    else if ( n < 10 ) {
                        l += printf( "%*s", 10 - n, " " );
                    }

                    n  = printf( " %3ux", ptr->start_number - j );
                    n += print_fraction( ptr->survivors[ j + k ] * 100, total, 0 );
                    n += printf( "%%" );

                    l += n;
                }
            }
        }

        print_line();

        ptr = ptr->next;
    }

    /* Show total losses */

    ptr = list;
    while ( ptr != NULL && ptr->average_casualties == 0 ) {

        this = ptr;
        ptr = ptr->next;
        free( this );
    }

    if ( ptr != NULL ) {

        print_line();
        printf( "    Average losses : " );

        n = 0;
        do {

            this = ptr;
            ptr = ptr->next;

            casualties = this->average_casualties;
            if ( casualties != 0 ) {

                last = NULL;
                scan = ptr;
                while ( scan != NULL ) {

                    if ( scan->average_casualties == 0 || this->what != scan->what ) {

                        last = scan;
                        scan = scan->next;
                    }
                    else {

                        casualties += scan->average_casualties;

                        if ( last == NULL ) {

                            ptr = scan->next;
                            free( scan );
                            scan = ptr;
                        }
                        else {

                            last->next = scan->next;
                            free( scan );
                            scan = last->next;
                        }
                    }
                }

                if ( n == 0 ) {
                    n = 21;
                }
                else {
                    printf( "%*s", n, " " );
                }

                pluralise( casualties, this->what->name );
                print_line();
            }

            free( this );
        }
        while ( ptr != NULL );
    }
}

static void process_data_line (
    char *str,
    char *filename,
    UINT  line_number,
    BOOL  overwrite )
{
    UINT    l;
    DEFN   *new_defn;
    PLURAL *new_plural;
    char   *code;
    char   *name;
    UINT    type;
    UINT    attack;
    UINT    sea_attack;
    UINT    defense;
    UINT    sea_defense;
    UINT    missile;
    UINT    saving_roll;

    while ( *str == ' ' || *str == '\t' ) {
        str ++;
    }

    code = str;
    while ( *str != ';' && *str != '\0' && *str != '\n' && *str != '\r' ) {
        str ++;
    }

    if ( *str != ';' ) {

        printf( "Invalid '%s' file at line %u\n", filename, line_number );
        printf( "First field (abbreviated name) must be terminated by a semi-colon ';'\n" );
        exit( 1 );
    }

    *str = '\0';

    str ++;
    while ( *str == ' ' || *str == '\t' ) {
        str ++;
    }

    name = str;
    while ( *str != ';' && *str != '\0' && *str != '\n' && *str != '\r' ) {
        str ++;
    }

    if ( *str != ';' ) {

        printf( "Invalid '%s' file at line %u\n", filename, line_number );
        printf( "Second field (full name) must be terminated by a semi-colon ';'\n" );
        exit( 1 );
    }

    *str = '\0';

    str ++;
    while ( *str == ' ' || *str == '\t' ) {
        str ++;
    }

    if ( !isdigit( *str )) {

        printf( "Invalid '%s' file at line %u\n", filename, line_number );
        printf( "Third field (type) must be an integer\n" );
        exit( 1 );
    }

    type = *str - '0';

    str ++;
    while ( *str == ' ' || *str == '\t' ) {
        str ++;
    }

    if ( type == TYPE_PLURAL ) {

        /* printf( "%s->%s\n", code, name ); */

        if ( *str != '#' && *str != '\0' && *str != '\n' && *str != '\r' ) {
            printf( "Invalid '%s' file at line %u\n", filename, line_number );
            printf( "Singular->plural transformations (type=6) have only 3 fields\n" );
            printf( " eg: @ singular_ending plural_ending 6 [# comment]\n" );
            exit( 1 );
        }

        l = strlen( code );
        new_plural = plural_list;
        while ( new_plural != NULL &&
               ( new_plural->length_in != l ||
                 memcmp( new_plural->data_in, code, l ) != 0 )) {
            new_plural = new_plural->next;
        }

        if ( new_plural != NULL ) {

            if ( overwrite == FALSE ) {

                printf( "Invalid '%s' file at line %u\n", filename, line_number );
                printf( "Singular ending '%s' used more than once\n", code );
                exit( 1 );
            }

            free( new_plural->data_out );
        }
        else {

            new_plural = (PLURAL *)malloc( sizeof( *new_plural ));
            if ( new_plural == NULL ) {

                printf( "Out of memory while reading '%s' file at line %u\n", filename, line_number );
                exit( 1 );
            }

            new_plural->next = plural_list;
            plural_list = new_plural;

            new_plural->length_in = l;
            new_plural->data_in = (char *)malloc( l + 1 );

            if ( new_plural->data_in == NULL ) {

                printf( "Out of memory while reading '%s' file at line %u\n", filename, line_number );
                exit( 1 );
            }

            memcpy( new_plural->data_in, code, l + 1 );
        }

        l = strlen( name );
        new_plural->length_out = l;
        new_plural->data_out = (char *)malloc( l + 1 );

        if ( new_plural->data_out == NULL ) {

            printf( "Out of memory while reading '%s' file at line %u\n", filename, line_number );
            exit( 1 );
        }

        memcpy( new_plural->data_out, name, l + 1 );
    }
    else {

        if ( !isdigit( *str )) {

            printf( "Invalid '%s' file at line %u\n", filename, line_number );
            printf( "Fourth field (attack rating) must be an integer\n" );
            exit( 1 );
        }

        attack = 0;
        while ( isdigit( *str )) {
            attack = attack * 10 + *str++ - '0';
        }

        while ( *str == ' ' || *str == '\t' ) {
            str ++;
        }

        if ( *str != '(' ) {

            sea_attack = attack;
        }
        else {

            str ++;
            if ( !isdigit( *str )) {

                printf( "Invalid '%s' file at line %u\n", filename, line_number );
                printf( "Open bracket '(' must be followed by an integer\n" );
                exit( 1 );
            }

            sea_attack = 0;
            while ( isdigit( *str )) {
                sea_attack = sea_attack * 10 + *str++ - '0';
            }

            while ( *str == ' ' || *str == '\t' ) {
                str ++;
            }

            if ( *str != ')' ) {

                printf( "Invalid '%s' file at line %u\n", filename, line_number );
                printf( "Close bracket ')' expected after '(' integer\n" );
                exit( 1 );
            }

            str ++;
            while ( *str == ' ' || *str == '\t' ) {
                str ++;
            }
        }

        if ( !isdigit( *str )) {

            printf( "Invalid '%s' file at line %u\n", filename, line_number );
            printf( "Fifth field (defense rating) must be an integer\n" );
            exit( 1 );
        }

        defense = 0;
        while ( isdigit( *str )) {
            defense = defense * 10 + *str++ - '0';
        }

        while ( *str == ' ' || *str == '\t' ) {
            str ++;
        }

        if ( *str != '(' ) {

            sea_defense = defense;
        }
        else {

            str ++;
            if ( !isdigit( *str )) {

                printf( "Invalid '%s' file at line %u\n", filename, line_number );
                printf( "Open bracket '(' must be followed by an integer\n" );
                exit( 1 );
            }

            sea_defense = 0;
            while ( isdigit( *str )) {
                sea_defense = sea_defense * 10 + *str++ - '0';
            }

            while ( *str == ' ' || *str == '\t' ) {
                str ++;
            }

            if ( *str != ')' ) {

                printf( "Invalid '%s' file at line %u\n", filename, line_number );
                printf( "Close bracket ')' expected after '(' integer\n" );
                exit( 1 );
            }

            str ++;
            while ( *str == ' ' || *str == '\t' ) {
                str ++;
            }
        }

        missile = 0;
        saving_roll = 0;
        if ( isdigit( *str )) {

            while ( isdigit( *str )) {
                missile = missile * 10 + *str++ - '0';
            }

            while ( *str == ' ' || *str == '\t' ) {
                str ++;
            }

            if ( isdigit( *str )) {

                while ( isdigit( *str )) {
                    saving_roll = saving_roll * 10 + *str++ - '0';
                }

                while ( *str == ' ' || *str == '\t' ) {
                    str ++;
                }
            }
        }

        /* printf( "%s %s %u %u(%u) %u(%u) %u %u\n",
                   code, name, type,
                   attack, sea_attack,
                   defense, sea_defense,
                   missile, saving_roll ); */

        if ( *str != '#' && *str != '\0' && *str != '\n' && *str != '\r' ) {

            printf( "Invalid '%s' file at line %u\n", filename, line_number );
            printf( "Unit definitions (type=0 to 5) have 5, 6 or 7 fields\n" );
            printf( " eg: @ code full_name type attack defense [missile] [save] [# comment]\n" );
            exit( 1 );
        }

        l = strlen( code );
        new_defn = definition;
        while ( new_defn != NULL &&
               ( strlen( new_defn->code ) != l ||
                 memcmp( new_defn->code, code, l ) != 0 )) {
            new_defn = new_defn->next;
        }

        if ( new_defn != NULL ) {

            if ( overwrite == FALSE ) {

                printf( "Invalid '%s' file at line %u\n", filename, line_number );
                printf( "Unit code '%s' used more than once\n", code );
                exit( 1 );
            }

            free( new_defn->name );
        }
        else {

            new_defn = (DEFN *)malloc( sizeof( *new_defn ));
            if ( new_defn == NULL ) {

                printf( "Out of memory while reading '%s' file at line %u\n", filename, line_number );
                exit( 1 );
            }

            new_defn->next = definition;
            definition = new_defn;

            l ++;
            new_defn->code = (char *)malloc( l );

            if ( new_defn->code == NULL ) {

                printf( "Out of memory while reading '%s' file at line %u\n", filename, line_number );
                exit( 1 );
            }

            memcpy( new_defn->code, code, l );
        }

        new_defn->type        = type;
        new_defn->attack      = attack;
        new_defn->sea_attack  = sea_attack;
        new_defn->defense     = defense;
        new_defn->sea_defense = sea_defense;
        new_defn->missile     = missile;
        new_defn->saving_roll = saving_roll;

        l = strlen( name ) + 1;
        new_defn->name = (char *)malloc( l );

        if ( new_defn->name == NULL ) {

            printf( "Out of memory while reading '%s' file at line %u\n", filename, line_number );
            exit( 1 );
        }

        memcpy( new_defn->name, name, l );
    }
}

static void read_data_file (
    char *root_name )
{
    UINT    i, l, n;
    FILE   *data_file;
    char    buffer[ 513 ];
    char    filename[ 30 ];
    char   *str;
    UINT    line_number;

    l = strlen( root_name );
    if ( l == 0 ) {

        strcpy( filename, "fight.dat" );
    }
    else {

        /* Cope with alternate program names. The datafile we will look
         * for consists of the program name with a .dat extension replacing
         * whatever extension it uses.
         */

        i = l - 1;
        n = i;
        while ( i > 0 &&
                root_name[ i ] != '\\' &&
                root_name[ i ] != '/' &&
               ( i != 1 || root_name[ i ] != ':' )) {

            if ( root_name[ i ] == '.' ) {

                n = i;
            }

            i --;
        }

        if ( root_name[ i ] == '\\' ||
             root_name[ i ] == '/' ||
            ( i == 1 && root_name[ i ] == ':' )) {

            i ++;
        }

        if ( 'a' <= root_name[ n + 1 ] && root_name[ n + 1 ] <= 'z' ) {
            if ( strcmp( root_name + n, ".exe" ) == 0 ) {

                l = n;
            }
        }
        else {
            if ( strcmp( root_name + n, ".EXE" ) == 0 ) {

                l = n;
            }
        }

        if ( i >= l || (( l - i ) + 5 ) > sizeof( filename )) {

            strcpy( filename, "fight.dat" );
        }
        else {

            if (( l + 5 ) <= sizeof( filename )) {

                memcpy( filename, root_name, l );
            }
            else {

                l -= i;
                memcpy( filename, root_name + i, l );
            }

            if ( 'a' <= root_name[ i ] && root_name[ i ] <= 'z' ) {

                memcpy( filename + l, ".dat", 5 );
            }
            else {

                memcpy( filename + l, ".DAT", 5 );
            }
        }
    }

    data_file = fopen( filename, "r" );
    if ( data_file == NULL ) {

        printf( "Cannot find '%s' file\n", filename );
        exit( 1 );
    }

    definition = NULL;
    plural_list = NULL;
    line_number = 0;
    while ( feof( data_file ) == FALSE &&
            fgets( buffer, sizeof( buffer ) - 1, data_file ) != NULL ) {

        line_number ++;

        str = buffer;
        while ( *str == ' ' || *str == '\t' ) {
            str ++;
        }

        if ( *str == '@' ) {

            process_data_line( str + 1, filename, line_number, FALSE );
        }
        else if ( *str != '#' && *str != '\0' && *str != '\n' && *str != '\r' ) {

            printf( "Invalid '%s' file at line %u\n", filename, line_number );
            printf( "Lines must begin with @, # or be blank\n" );
            exit( 1 );
        }
    }

    fclose( data_file );
}

static void partial_reset (
    PARTIAL_RESULTS *data )
{
    data->code = NULL;
    data->behind = 0;
    data->number = 1;
    data->look_for_behind = FALSE;
}

static void clear_expectations (
    PARTIAL_RESULTS *data,
    char            *filename,
    UINT             line_number )
{
    if ( data->expect_run_time == TRUE ) {

        printf( "\nA -T parameter must be immediately followed by a number\n" );
        error( filename, line_number );
    }

    if ( data->expect_num_of_battles == TRUE ) {

        printf( "\nA -N parameter must be immediately followed by a number\n" );
        error( filename, line_number );
    }

    if ( data->look_for_filename == TRUE ) {

        printf( "\nA -F parameter must be immediately followed by a filename\n" );
        error( filename, line_number );
    }

    data->last_was_attacker_break = FALSE;
    data->last_was_defender_break = FALSE;
    data->expect_run_time = FALSE;
    data->expect_num_of_battles = FALSE;
    data->look_for_filename = FALSE;
    data->expect_number = FALSE;
}

static void full_reset (
    PARTIAL_RESULTS *data )
{
    partial_reset( data );

    data->stack = NULL;
    data->attacker_break = 50;
    data->defender_break = 0;
    data->attd_preset = 50;
    data->dttd_preset = 50;
    data->raining = FALSE;
    data->windy = FALSE;
    data->verbose = FALSE;
    data->debug = FALSE;
    data->num_of_battles = 0;
    data->list = NULL;
    data->run_time = 0L;
    data->look_for_filename = FALSE;
    data->last_was_attacker_break = FALSE;
    data->last_was_defender_break = FALSE;
    data->expect_run_time = FALSE;
    data->expect_num_of_battles = FALSE;
    data->expect_number = FALSE;
}

static void add_file_name (
    PARTIAL_RESULTS *data,
    char            *name,
    UINT             length )
{
    FILE_NAMES *scan;
    FILE_NAMES *last;

    last = NULL;
    scan = data->list;
    while ( scan != NULL &&
           ( scan->length != length ||
             memcmp( scan->name, name, length ) != 0 )) {

        last = scan;
        scan = scan->next;
    }

    if ( scan == NULL ) {

        scan = (FILE_NAMES *)malloc( sizeof( *scan ));
        if ( scan == NULL ) {

            printf( "Out of memory while storing filename '%*s'\n", length, name );
            exit( 1 );
        }

        scan->name = malloc( length + 1 );
        if ( scan->name == NULL ) {

            printf( "Out of memory while storing filename '%*s'\n", length, name );
            exit( 1 );
        }

        memcpy( scan->name, name, length );

        scan->name[ length ] = '\0';
        scan->next = NULL;
        scan->length = length;

        if ( last == NULL ) {
            data->list = scan;
        }
        else {
            last->next = scan;
        }
    }
}

static void interpret_param (
    char            *in_param,
    PARTIAL_RESULTS *data,
    char            *filename,
    UINT             line_number )
{
    LONG  value;
    char *param;
    char *str;
    BOOL  number_allowed;
    BOOL  exit_loop;

    param = in_param;
    while ( *param != '\0' ) {

        if ( data->look_for_filename &&
             *param != '\0' &&
             *param != ' '  &&
             *param != '\t' &&
             *param != '\r' &&
             *param != '\n' ) {

            str = param;
            while ( *param != '\0' &&
                    *param != ' '  &&
                    *param != '\t' &&
                    *param != '\r' &&
                    *param != '\n' ) {
                param ++;
            }
            add_file_name( data, str, param - str );
            data->look_for_filename = FALSE;
        }
        else if ( isalpha( *param )) {

            clear_expectations( data, filename, line_number );

            if ( data->code != NULL ) {

                data->stack = add_member( &battle.side2,
                                          data->code,
                                          data->number,
                                          data->behind,
                                          FALSE,
                                          data->stack,
                                          filename,
                                          line_number );

                partial_reset( data );
            }

            str = param;
            while ( isalpha( *++param )) {
                /* Skip along */
            }

            data->code = find_code( str, param - str, filename, line_number );
            data->expect_number = TRUE;
        }
        else if ( *param == '[' || *param == '(' || *param == '{' ) {

            clear_expectations( data, filename, line_number );

            if ( data->code != NULL ) {

                data->stack = add_member( &battle.side2,
                                          data->code,
                                          data->number,
                                          data->behind,
                                          TRUE,
                                          data->stack,
                                          filename,
                                          line_number );

                partial_reset( data );
            }
            else {

                printf( "\nUnexpected open bracket, '%s' in '%s'\n", param, in_param );
                error( filename, line_number );
            }

            param ++;
        }
        else if ( *param == ']' || *param == ')' || *param == '}' ) {

            clear_expectations( data, filename, line_number );

            if ( data->code != NULL ) {

                data->stack = add_member( &battle.side2,
                                          data->code,
                                          data->number,
                                          data->behind,
                                          FALSE,
                                          data->stack,
                                          filename,
                                          line_number );

                partial_reset( data );
            }

            if ( data->stack != NULL ) {

                data->stack = data->stack->stacked_under;
            }
            else {

                printf( "\nUnexpected close bracket, '%s' in '%s'\n", param, in_param );
                error( filename, line_number );
            }

            param ++;
        }
        else if ( *param == '@' ) {

            clear_expectations( data, filename, line_number );

            if ( data->code == NULL ||
                 data->look_for_behind == TRUE ||
                 data->behind != 0 ) {

                printf( "\nUnexpected behind qualifier, '%s' in '%s'\n", param, in_param );
                error( filename, line_number );
            }

            data->look_for_behind = TRUE;
            param ++;
        }
        else if ( *param == '*' || *param == ':' || *param == '!' ) {

            clear_expectations( data, filename, line_number );

            if ( data->code != NULL ) {

                data->stack = add_member( &battle.side2,
                                          data->code,
                                          data->number,
                                          data->behind,
                                          FALSE,
                                          data->stack,
                                          filename,
                                          line_number );

                partial_reset( data );
            }

            if ( data->stack != NULL ) {

                printf( "\nOne or more unmatched open brackets before sides separator\n" );
                error( filename, line_number );
            }

            if ( battle.side1.list_first != NULL ) {
                printf( "\nUnexpected second sides separator, '%s' in '%s'\n", param, in_param );
                error( filename, line_number );
            }

            if ( battle.side2.list_first == NULL ) {
                printf( "\nFirst side has not been defined, must precede side separator\n" );
                error( filename, line_number );
            }

            battle.side1 = battle.side2;

            battle.side2.list_first = NULL;
            battle.side2.list_last = NULL;
            battle.side2.victories = 0;
            battle.side2.value = 0;
            battle.side2.start_value = 0;
            battle.side2.threshold = 0;

            param ++;
        }
        else if ( isdigit( *param )) {

            value = 0L;
            do {
                value = value * 10L + ( *param++ - '0' );
            }
            while ( isdigit( *param ));

            if ( data->look_for_behind == TRUE ) {

                if ( value > 9L ) {

                    printf( "\nBehind status must be in range 0-9, '%s' in '%s'\n", param, in_param );
                    error( filename, line_number );
                }

                data->behind = (UINT)value;
                data->look_for_behind = FALSE;
            }
            else if ( data->last_was_attacker_break == TRUE ||
                      data->last_was_defender_break == TRUE ) {

                if ( value > 100L ) {

                    printf( "\nBreak point must be in range 0-100, '%s' in '%s'\n", param, in_param );
                    error( filename, line_number );
                }

                if ( data->last_was_attacker_break == TRUE ) {

                    data->attd_preset = (UINT)value;
                    data->attacker_break = (UINT)value;
                    data->last_was_attacker_break = FALSE;
                }

                if ( data->last_was_defender_break == TRUE ) {

                    data->dttd_preset = (UINT)value;
                    data->defender_break = (UINT)value;
                    data->last_was_defender_break = FALSE;
                }
            }
            else if ( data->expect_run_time == TRUE ) {

                data->run_time = value;
                data->expect_run_time = FALSE;
            }
            else if ( data->expect_num_of_battles == TRUE ) {

                data->num_of_battles = value;
                data->expect_num_of_battles = FALSE;
            }
            else if ( data->expect_number == TRUE ) {

                if ( value == 0 ) {

                    printf( "\nNumber must be greater than 0, '%s' in '%s'\n", param, in_param );
                    error( filename, line_number );
                }

                data->number = (UINT)value;
                data->expect_number = FALSE;
            }
            else {
                printf( "\nUnexpected number, '%s' in '%s'\n", param, in_param );
                error( filename, line_number );
            }

            clear_expectations( data, filename, line_number );
        }
        else if ( *param == ' ' ) {
            param ++;
        }
        else if ( *param == '?' ) {
            print_spec();
            exit( 0 );
        }
        else if ( *param == '-' ) {

            exit_loop = FALSE;

            number_allowed = FALSE;

            param ++;
            do {

                switch ( *param ) {
                    case 'a':
                    case 'A':
                        clear_expectations( data, filename, line_number );
                        data->attacker_break = 0;
                        data->defender_break = data->dttd_preset;
                        data->attd_preset = 0;
                        data->last_was_attacker_break = TRUE;
                        number_allowed = TRUE;
                        param ++;
                    break;

                    case 'b':
                    case 'B':
                        clear_expectations( data, filename, line_number );
                        data->attacker_break = 0;
                        data->defender_break = 0;
                        data->last_was_attacker_break = TRUE;
                        data->last_was_defender_break = TRUE;
                        number_allowed = TRUE;
                        param ++;
                    break;

                    case 'd':
                    case 'D':
                        clear_expectations( data, filename, line_number );
                        data->attacker_break = data->attd_preset;
                        data->defender_break = 0;
                        data->dttd_preset = 0;
                        data->last_was_defender_break = TRUE;
                        number_allowed = TRUE;
                        param ++;
                    break;

                    case 'e':
                    case 'E':
                        number_allowed = FALSE;
                        clear_expectations( data, filename, line_number );
                        data->attacker_break = data->attd_preset;
                        data->defender_break = data->dttd_preset;
                        param ++;
                    break;

                    case 'f':
                    case 'F':
                        clear_expectations( data, filename, line_number );
                        data->look_for_filename = TRUE;
                        exit_loop = TRUE;
                        param ++;
                    break;

                    case 'h':
                    case 'H':
                    case '?':
                        clear_expectations( data, filename, line_number );
                        print_spec();
                        exit( 0 );
                    break;

                    case 'n':
                    case 'N':
                        clear_expectations( data, filename, line_number );
                        data->expect_num_of_battles = TRUE;
                        exit_loop = TRUE;
                        param ++;
                    break;

                    case 'p':
                    case 'P':
                        number_allowed = FALSE;
                        clear_expectations( data, filename, line_number );
                        data->debug = TRUE;
                        param ++;
                    break;

                    case 'r':
                    case 'R':
                        number_allowed = FALSE;
                        clear_expectations( data, filename, line_number );
                        data->raining = TRUE;
                        param ++;
                    break;

                    case 't':
                    case 'T':
                        clear_expectations( data, filename, line_number );
                        data->expect_run_time = TRUE;
                        exit_loop = TRUE;
                        param ++;
                    break;

                    case 'v':
                    case 'V':
                        number_allowed = FALSE;
                        clear_expectations( data, filename, line_number );
                        data->verbose = TRUE;
                        param ++;
                    break;

                    case 'w':
                    case 'W':
                        number_allowed = FALSE;
                        clear_expectations( data, filename, line_number );
                        data->windy = TRUE;
                        param ++;
                    break;

                    default:
                        if ( number_allowed == TRUE && isdigit( *param )) {
                            exit_loop = TRUE;
                        }
                        else {
                            printf( "\nUnrecognised parameter, '%s' in '%s'\n", param, in_param );
                            error( filename, line_number );
                        }
                    break;
                }
            }
            while ( *param != '\0' &&
                    *param != ' '  &&
                    *param != '\t' &&
                    *param != '\r' &&
                    *param != '\n' &&
                    exit_loop == FALSE );
        }
        else {
            printf( "\nUnrecognised parameter, '%s' in '%s'\n", param, in_param );
            error( filename, line_number );
        }
    }
}

static void end_interpret (
    PARTIAL_RESULTS *data,
    char            *filename,
    UINT             line_number )
{
    if ( data->code != NULL ) {

        data->stack = add_member( &battle.side2,
                                  data->code,
                                  data->number,
                                  data->behind,
                                  FALSE,
                                  data->stack,
                                  filename,
                                  line_number );
    }

    if ( data->stack != NULL ) {

        printf( "\nOne or more unmatched open brackets\n" );
        error( filename, line_number );
    }

    if ( data->list == NULL && battle.side2.list_first == NULL ) {

        printf( "\nNo sides specified, two are needed for a battle\n" );
        error( filename, line_number );
    }

    if ( battle.side1.list_first == NULL && battle.side2.list_first != NULL ) {

        printf( "\nOnly one side specified, two are needed for a battle\n" );
        error( filename, line_number );
    }

    print_line();
}

static void reset_battle (
    void )
{
    battle.side1.list_first = NULL;
    battle.side1.list_last = NULL;
    battle.side1.victories = 0;
    battle.side1.value = 0;
    battle.side1.start_value = 0;
    battle.side1.threshold = 0;

    battle.side2.list_first = NULL;
    battle.side2.list_last = NULL;
    battle.side2.victories = 0;
    battle.side2.value = 0;
    battle.side2.start_value = 0;
    battle.side2.threshold = 0;
}

static void resolve_battle (
    PARTIAL_RESULTS *settings )
{
    LONG  total;

    if ( battle.side1.list_first != NULL ) {

        battle.side1.threshold = battle.side1.threshold * settings->attacker_break / 100;
        battle.side2.threshold = battle.side2.threshold * settings->defender_break / 100;

        if ( settings->raining == TRUE || settings->windy == TRUE ) {
            apply_wind_and_rain( battle.side1.list_first, settings->raining, settings->windy );
            apply_wind_and_rain( battle.side2.list_first, settings->raining, settings->windy );
        }

        if ( settings->num_of_battles > 0 ) {

            do_battle( settings->num_of_battles, settings->run_time, settings->debug );
        }
        else if ( settings->debug == TRUE ) {

            do_battle( 1, settings->run_time, settings->debug );
        }
        else if ( settings->run_time > 0 ) {

            do_battle( 0xFFFFFFFF, settings->run_time, settings->debug );
        }
        else {

            do_battle( 5000, settings->run_time, settings->debug );
        }

        total = battle.side1.victories + battle.side2.victories;

        if ( settings->raining == TRUE || settings->windy == TRUE ) {

            if ( settings->raining == TRUE ) {

                printf( "It is raining, all archer missile ratings reduced to zero" );
                print_line();
            }

            if ( settings->windy == TRUE ) {

                printf( "It is windy, all missile ratings halved" );
                print_line();
            }

            print_line();
        }

        printf( "ATTACKER" );
        if ( settings->attacker_break == 0 ) {
            printf( " (Fighting to the death)" );
        }
        else {
            printf( " (Break at %u%%)", settings->attacker_break );
        }
        printf( " :" );
        print_line();
        print_line();
        print_result( battle.side1.list_first, total, settings->verbose );
        print_line();

        printf( "DEFENDER" );
        if ( settings->defender_break == 0 ) {
            printf( " (Fighting to the death)" );
        }
        else {
            printf( " (Break at %u%%)", settings->defender_break );
        }
        printf( " :" );
        print_line();
        print_line();
        print_result( battle.side2.list_first, total, settings->verbose );
        print_line();

        printf( "LIKELIHOOD OF WIN " );
        print_fraction( battle.side1.victories * 100, total, 2 );
        printf( "%%" );
        print_line();

        reset_battle();

        done_one = TRUE;
    }
}

static void read_parameter_file (
    char            *filename,
    PARTIAL_RESULTS *settings )
{
    UINT            l;
    FILE           *file;
    char            buffer[ 513 ];
    char           *str;
    char           *scan;
    UINT            line_number;
    PARTIAL_RESULTS new_settings;
    BOOL            more;

    file = fopen( filename, "r" );
    if ( file == NULL ) {

        printf( "Cannot open parameter file '%s'\n", filename );
        exit( 1 );
    }

    if ( done_one == TRUE ) {

        print_line();
        printf( "----------------" );
        print_line();
        print_line();
        done_one = FALSE;
    }

    printf( "Reading parameter file: %s", filename );
    print_line();
    print_line();

    more = FALSE;
    line_number = 0;
    new_settings = *settings;
    while ( feof( file ) == FALSE &&
            fgets( buffer, sizeof( buffer ) - 1, file ) != NULL &&
            time_to_die == FALSE ) {

        line_number ++;

        str = buffer;
        while ( *str == ' ' || *str == '\t' ) {
            str ++;
        }

        if ( *str == '@' ) {

            if ( more == TRUE ) {

                printf( "Improper continuation record in '%s' file at line %u\n", filename, line_number );
                exit( 1 );
            }

            process_data_line( str + 1, filename, line_number, TRUE );
        }
        else if ( *str != '#' && *str != '\0' && *str != '\n' && *str != '\r' ) {

            if ( done_one == TRUE ) {

                print_line();
                printf( "----------------" );
                print_line();
                print_line();
                printf( "Continuing with parameter file: %s", filename );
                print_line();
                print_line();
                done_one = FALSE;
            }

            printf( "  %s", buffer );

            more = FALSE;

            /* Continuation marker can be either before or after an  */
            /* optional comment field. A \ is a continuations marker */
            /* and a # is a comment.                                 */

            l = strlen( str );
            while ( l > 0 &&
                   ( str[ l - 1 ] == '\n' ||
                     str[ l - 1 ] == '\r' ||
                     str[ l - 1 ] == '\t' ||
                     str[ l - 1 ] == ' ' )) {
                l --;
            }

            if ( l > 0 && str[ l - 1 ] == '\\' ) {

                more = TRUE;
                l --;
            }

            scan = memchr( str, '#', l );
            if ( scan != NULL ) {

                l = scan - str;
                while ( l > 0 &&
                       ( str[ l - 1 ] == '\n' ||
                         str[ l - 1 ] == '\r' ||
                         str[ l - 1 ] == '\t' ||
                         str[ l - 1 ] == ' ' )) {
                    l --;
                }

                if ( l > 0 && str[ l - 1 ] == '\\' ) {

                    more = TRUE;
                    l --;
                }
            }

            if ( l > 0 ) {

                str[ l ] = '\0';
                interpret_param( str, &new_settings, filename, line_number );
            }

            if ( more == FALSE ) {

                end_interpret( &new_settings, filename, line_number );
                resolve_battle( &new_settings );
                new_settings = *settings;
            }
        }
    }

    if ( more == TRUE ) {

        printf( "Improper continuation record in '%s' file at line %u\n", filename, line_number );
        exit( 1 );
    }

    fclose( file );
}

int main (
    UINT  argc,
    char  *argv[] )
{
    UINT            i;
    char           *str;
    PARTIAL_RESULTS settings;
    FILE_NAMES     *next;
    FILE_NAMES    **ptr_to_next;

    time_to_die = FALSE;
    signal( SIGINT, kill_handler );

#ifdef CHECK_RANDOM
    clear_table();
#endif

    printf( "%s", version );
    output_line_count = 1;

    str = getenv( ENV_MAXIMUM_RUN_TIME );
    if ( str == NULL ) {
        abs_maximum_run_time = DEF_MAXIMUM_RUN_TIME;
    }
    else {
        abs_maximum_run_time = atol( str );
    }

    str = getenv( ENV_MAXIMUM_BATTLE_TIME );
    if ( str == NULL ) {
        maximum_run_time = DEF_MAXIMUM_BATTLE_TIME;
    }
    else {
        maximum_run_time = atol( str );
    }

    str = getenv( ENV_PROGRESS_INTERVAL );
    if ( str == NULL ) {
        progress_interval = DEF_PROGRESS_INTERVAL;
    }
    else {
        progress_interval = atol( str );
    }

    str = getenv( ENV_MAXIMUM_LINE_LIMIT );
    if ( str == NULL ) {
        maximum_line_limit = DEF_MAXIMUM_LINE_LIMIT;
    }
    else {
        maximum_line_limit = atol( str );
    }

    read_data_file( argv[ 0 ]);

    initialise_random_seed();
    reset_battle();
    full_reset( &settings );

    printf( "Parameters:" );

    for ( i = 1; i < argc; i ++ ) {

        printf( " %s", argv[ i ] );
        interpret_param( argv[ i ], &settings, NULL, 0 );
    }

    end_interpret( &settings, NULL, 0 );

    ptr_to_next = &settings.list;

    done_one = FALSE;
    for (;;) {

        resolve_battle( &settings );

        next = *ptr_to_next;
        if ( next == NULL || time_to_die == TRUE ) {
            break;
        }

        ptr_to_next = &next->next;
        read_parameter_file( next->name, &settings );
    }

    return 0;
}
