/* Scores and scoring in Xconq.
   Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING.  */

/* Scoring in Xconq does not happen by default; instead a game design
   may include one or more "scorekeepers", which are objects with
   properties that define how winning and losing will be decided.

   There are two kinds of functionality in scorekeepers.  One kind is
   the ability to end a game based on a specified condition, such as
   "the Star of Satyria enters Noblum".  The other is the accumulation
   of a numeric score.  A scorekeeper may include both, so it can (for
   instance) end the game the instant that a player's score reaches
   100, or 100 more than another player, or whatever.

   The indepside should not participate in scoring, so the iterations
   in this file generally iterate only over the "real" sides. */

#include "conq.h"
#include "kernel.h"

typedef struct a_score_record {
    char *gamename;
    Obj *sides;
    Obj *raw;
    int numturns;
    Obj *varsets;
    struct a_score_record *next;
} ScoreRecord;

/* This is true when the given side should be tested against the given
   scorekeeper. */

#define scorekeeper_applicable(side,sk)  \
  ((side)->ingame && (side_in_set((side), (sk)->whomask)))

/* Iteration over all recorded scores. */

#define for_all_score_records(sr)  \
  for ((sr) = records; (sr) != NULL; (sr) = (sr)->next)

static int read_scorefile(void);
static int interp_score_record(Obj *form);
static char *basic_player_name(Player *player);
static void eval_sk_last_side_wins(Scorekeeper *sk);
static void eval_sk_last_alliance_wins(Scorekeeper *sk);
static void score_variant_desc(ScoreRecord *sr, char *varbuf);

/* The head of the list of scorekeepers. */

Scorekeeper *scorekeepers;

/* The end of the list of scorekeepers. */

Scorekeeper *last_scorekeeper;

/* The total number of scorekeepers defined. */

int numscorekeepers;

int nextskid;

/* The number of scorekeepers maintaining numeric scores. */

int numscores;

/* True if any pre-turn scorekeepers are defined. */

int any_pre_turn_scores;

/* True if any post-turn scorekeepers are defined. */

int any_post_turn_scores;

/* True if any post-action scorekeepers are defined. */

int any_post_action_scores;

/* True if any post-event scorekeepers are defined. */

int any_post_event_scores;

/* True if any turn-specific scorekeepers are defined. */

int any_turn_specific_scores;

/* The count of sides in the game when the last-side-wins scorekeeper
   first tested. */

static int num_sides_originally;

static int only_checking;

static int we_have_a_winner;

ScoreRecord *records;

/* Clear out any possible scorekeepers. */

void
init_scorekeepers(void)
{
    scorekeepers = last_scorekeeper = NULL;
    numscorekeepers = 0;
    nextskid = 1;
    any_pre_turn_scores = FALSE;
    any_post_turn_scores = FALSE;
    any_post_action_scores = FALSE;
    any_post_event_scores = FALSE;
    any_turn_specific_scores = FALSE;
}

Scorekeeper *
create_scorekeeper(void)
{
    Scorekeeper *sk = (Scorekeeper *) xmalloc(sizeof(Scorekeeper));

    /* Initialize any nonzero fields. */
    sk->id = nextskid++;
    sk->when = lispnil;
    sk->who = lispnil;
    sk->whomask = ALLSIDES;
    sk->knownto = lispnil;
    sk->trigger = lispnil;
    sk->body = lispnil;
    sk->record = lispnil;
    sk->notes = lispnil;
    sk->scorenum = -1;
    /* Init the initial score to a disallowed value, this keeps it off the
       side's scorecard. */
    sk->initial = -10001;
    sk->triggered = FALSE;
    /* Add the new scorekeeper to the end of the list. */
    if (last_scorekeeper != NULL) {
	last_scorekeeper->next = sk;
	last_scorekeeper = sk;
    } else {
	scorekeepers = last_scorekeeper = sk;
    }
    ++numscorekeepers;
    return sk;
}

Scorekeeper *
find_scorekeeper(int id)
{
    Scorekeeper *sk;

    for_all_scorekeepers(sk) {
	if (sk->id == id)
	  return sk;
    }
    return NULL;
}

/* Allocate and fill in the initial score records for each side.  This
   must happen after all scorekeepers have been defined. */

void
init_scores(void)
{
    int score;
    Side *side;
    Scorekeeper *sk;
    Obj *savedscores, *when;

    /* First count and index all the scorekeepers that maintain
       a numeric score. */
    numscores = 0;
    for_all_scorekeepers(sk) {
    	if (sk->initial != -10001) {
    	    sk->scorenum = numscores++;
    	}
    }
    /* Allocate an appropriately-sized scorecard for each side.  Note that a
       particular position in the scorecard might not apply to all sides. */
    for_all_sides(side) {
	/* Collect any Lisp object that might have been stashed here
	   while reading a game module. */
	savedscores = side->rawscores;
	side->scores = NULL;
	if (numscores > 0) {
	    side->scores = (short *) xmalloc(numscores * sizeof(short));
	    for_all_scorekeepers(sk) {
		score = sk->initial;
		/* Collect a saved score if there is one to collect. */
		if (savedscores != NULL
		    && savedscores != lispnil
		    && numberp(car(savedscores))) {
		    score = c_number(car(savedscores));
		    savedscores = cdr(savedscores);
		}
		side->scores[sk->scorenum] = score;
	    }
	}
    }
    /* Some kinds of scorekeepers are expensive to run, so we set flags to
       indicate directly that we need to make the check. */
    for_all_scorekeepers(sk) {
    	when = sk->when;
    	if (consp(when)) {
	    if (cdr(when) != lispnil) {
		any_turn_specific_scores = TRUE;
	    }
	    when = car(when);
	}
	if (match_keyword(when, K_BEFORE_TURN)) {
	    any_pre_turn_scores = TRUE;
	}
	if (when == lispnil || match_keyword(when, K_AFTER_TURN)) {
	    any_post_turn_scores = TRUE;
	}
	if (match_keyword(when, K_AFTER_ACTION)) {
	    any_post_action_scores = TRUE;
	}
	if (match_keyword(when, K_AFTER_EVENT)) {
	    any_post_event_scores = TRUE;
	}
    }
    /* Compute the number of sides in the game initially.  This is so
       we don't force the game to end because (for whatever reason)
       only one side was active from the beginning. */
    num_sides_originally = 0;
    for_all_sides(side) {
	if (side->ingame)
	  ++num_sides_originally;
    }
}

/* Test all the scorekeepers that should be run immediately before any
   side moves anything. */

void
check_pre_turn_scores(void)
{
    Side *side;
    Scorekeeper *sk;

    if (any_pre_turn_scores) {
	for_all_scorekeepers(sk) {
	    if (match_keyword(sk->when, K_BEFORE_TURN)) {
		for_all_sides(side) {
		    if (scorekeeper_applicable(side, sk)) {
			run_scorekeeper(side, sk); 
		    } else {
		    	Dprintf("sk %d not applicable to %s\n",
		    		sk->id, side_desig(side));
		    }
		}
	    }
	}
    }
}

/* Test all the scorekeepers that should be run only at the end of a turn. */

void
check_post_turn_scores(void)
{
    Side *side;
    Scorekeeper *sk;

    if (any_post_turn_scores) {
	for_all_scorekeepers(sk) {
	    Dprintf("Checking post-turn scorekeeper %d\n", sk->id);
	    if (sk->when == lispnil || match_keyword(sk->when, K_AFTER_TURN)) {
		/* Decide whether the scorekeeper applies to one side
                   or to the whole game. */
	    	if (symbolp(sk->body)
	    	    && match_keyword(sk->body, K_LAST_SIDE_WINS)) {
		    eval_sk_last_side_wins(sk);
	    	} else if (symbolp(sk->body)
			   && match_keyword(sk->body, K_LAST_ALLIANCE_WINS)) {
		    eval_sk_last_alliance_wins(sk);
	    	} else {
		    for_all_sides(side) {
			if (scorekeeper_applicable(side, sk)) {
			    run_scorekeeper(side, sk); 
			} else {
			    Dprintf("sk %d not applicable to %s\n",
				    sk->id, side_desig(side));
			}
		    }
		}
	    }
	}
    }
}

void
check_post_action_scores(void)
{
    Side *side;
    Scorekeeper *sk;

    if (any_post_action_scores) {
	Dprintf("Checking post-action scorekeepers\n");
	for_all_scorekeepers(sk) {
	    if (match_keyword(sk->when, K_AFTER_ACTION)) {
		if (sk->trigger == lispnil || sk->triggered) {
		    for_all_sides(side) {
			if (scorekeeper_applicable(side, sk)) {
			    run_scorekeeper(side, sk);
		        } else {
		    	    Dprintf("sk %d not applicable to %s\n",
		    		    sk->id, side_desig(side));
			}
		    }
		}
	    }
	}
    }
}

void
check_post_event_scores(void)
{
    Side *side;
    Scorekeeper *sk;

    if (any_post_event_scores) {
	Dprintf("Checking post-event scorekeepers\n");
	for_all_scorekeepers(sk) {
	    if (match_keyword(sk->when, K_AFTER_EVENT)) {
		if (sk->trigger == lispnil || sk->triggered) {
		    for_all_sides(side) {
			if (scorekeeper_applicable(side, sk)) {
			    run_scorekeeper(side, sk); 
		        } else {
		    	    Dprintf("sk %d not applicable to %s\n",
		    		    sk->id, side_desig(side));
			}
		    }
		}
	    }
	}
    }
}

/* This is what actually does the test and effect of the scorekeeper
   on the given side.  This can be expensive to run. */

void
run_scorekeeper(Side *side, Scorekeeper *sk)
{
    eval_sk_form(side, sk, sk->body);
}

int
eval_sk_form(Side *side, Scorekeeper *sk, Obj *form)
{
    int val;
    char *formtype;
    Obj *test, *clauses, *clause, *rest;

    if (symbolp(form)) {
        if (match_keyword(form, K_LAST_SIDE_WINS)) {
            eval_sk_last_side_wins(sk);
	    return 0;
        } else if (match_keyword(form, K_LAST_ALLIANCE_WINS)) {
            eval_sk_last_alliance_wins(sk);
	    return 0;
        } else if (boundp(form)) {
            return 0;
        } else {
            syntax_error(form, "scorekeeper body");
            return 0;
        }
    } else if (consp(form) && symbolp(car(form))) {
	formtype = c_string(car(form));
	switch (keyword_code(formtype)) {
	case K_IF:
	  test = cadr(form);
	  if (eval_sk_test(side, sk, test)) {
	      eval_sk_form(side, sk, caddr(form));
	  }
	  break;
	case K_COND:
	  /* Interpret the traditional cond form. */
	  for_all_list(cdr(form), clauses) {
	      clause = car(clauses);
	      test = car(clause);
	      if (eval_sk_test(side, sk, test)) {
		  for_all_list(cdr(clause), rest) {
		      eval_sk_form(side, sk, car(rest));
		  }
		  /* Don't look at any more clauses. */
		  break;
	      }
	  }
	  break;
	case K_WIN:
	  if (side->ingame && !only_checking)
	    side_wins(side, sk->id);
	  break;
	case K_LOSE:
	  if (side->ingame && !only_checking)
	    side_loses(side, NULL, sk->id);
	  break;
	case K_END:
	  if (!only_checking)
	    all_sides_draw();
	  break;
	case K_SET:
	  if (sk->scorenum >= 0) {
	      side->scores[sk->scorenum] = eval_sk_form(side, sk, cadr(form));
	      return side->scores[sk->scorenum];
	  } else {
	      run_warning("Use of `set' in non-numeric scorekeeper %d",
			  sk->id);
	      return 0;
	  }
	case K_ADD:
	  if (sk->scorenum >= 0) {
	      if (cdr(form) != lispnil) {
		  val = eval_sk_form(side, sk, cadr(form));
	      } else {
		  val = 1;
	      }
	      side->scores[sk->scorenum] += val;
	      return side->scores[sk->scorenum];
	  } else {
	      run_warning("Use of `add' in non-numeric scorekeeper %d",
			  sk->id);
	      return 0;
	  }
	case K_SUM:
	  return sum_property(side, cdr(form));
	default:
	  run_warning("unknown form type `%s' in scorekeeper %d",
		      formtype, sk->id);
	}
	/* This is for those forms that don't really have values, but
	   may appear in a value-using context anyway. */
	return 0;
    } else if (numberp(form)) {
    	return c_number(form);
    } else {
        syntax_error(form, "scorekeeper body");
	return 0;
    }
}

int
sum_property(Side *side, Obj *form)
{
    int sum = 0, u, typevec[MAXUTYPES];
    Obj *sidelist = lispnil, *typelist = lispnil, *prop = lispnil;
    Obj *filter = lispnil;
    Unit *unit;
    
    if (typelist != lispnil) {
	for_all_unit_types(u)
	  typevec[u] = FALSE;
	/* (should interpret type list) */
    } else {
	for_all_unit_types(u)
	  typevec[u] = TRUE;
    }
    if (consp(form)) {
	prop = car(form);
	form = cdr(form);
    }
    if (sidelist != lispnil) {
    	/* (should let optional cadr designate some other side) */
    } else {
	for_all_side_units(side, unit) {
	    if (in_play(unit)
		&& typevec[unit->type]
		&& (filter == lispnil || TRUE /*filter allows through*/)) {
		if (prop != lispnil && symbolp(prop)) {
		    if (strcmp(c_string(prop), "point-value") == 0) {
			sum += point_value(unit);
		    } else {
			/* (should complain) */
		    }
		} else {
		    ++sum;
		}
	    }
	}
    }   
    return sum;
}

/* Return the point value of a particular unit. */

int
point_value(Unit *unit)
{
    /* Incomplete units are always worthless. */
    if (unit->cp > 0 && !completed(unit))
      return 0;
    if (unit_point_value(unit) >= 0)
      return unit_point_value(unit);
    return u_point_value(unit->type);
}

/* Return the sum of a side's units' point values. */

int
side_point_value(Side *side)
{
    Unit *unit;

    if (!side->point_value_valid) {
	side->point_value_cache = 0;
	for_all_side_units(side, unit) {
	    /* Note that we want to count units that may appear in
	       the future. */
	    if ((in_play(unit) && completed(unit))
		|| (alive(unit) && unit->cp < 0)) {
		side->point_value_cache += point_value(unit);
	    }
	}
	side->point_value_valid = TRUE;
    }
    return side->point_value_cache;
}

/* Evaluate a given form with respect to the given scorekeeper and side. */

int
eval_sk_test(Side *side, Scorekeeper *sk, Obj *form)
{
    char *formtype;
    Obj *arg1 = lispnil, *arg2 = lispnil, *rest;

    if (consp(form)) {
	formtype = c_string(car(form));
	if (consp(cdr(form)))
	  arg1 = cadr(form);
	if (consp(cddr(form)))
	  arg2 = caddr(form);
	switch (keyword_code(formtype)) {
	  case K_AND:
	    for_all_list(cdr(form), rest) {
		if (!eval_sk_form(side, sk, car(rest)))
		  return FALSE;
	    }
	    return TRUE;
	  case K_OR:
	    for_all_list(cdr(form), rest) {
		if (eval_sk_form(side, sk, car(rest)))
		  return TRUE;
	    }
	    return FALSE;
	  case K_NOT:
	    return (!eval_sk_form(side, sk, arg1));
	  case K_EQ:
	    return (eval_sk_form(side, sk, arg1) ==
		    eval_sk_form(side, sk, arg2));
	  case K_NE:
	    return (eval_sk_form(side, sk, arg1) !=
		    eval_sk_form(side, sk, arg2));
	  case K_LT:
	    return (eval_sk_form(side, sk, arg1) <
		    eval_sk_form(side, sk, arg2));
	  case K_LE:
	    return (eval_sk_form(side, sk, arg1) <=
		    eval_sk_form(side, sk, arg2));
	  case K_GT:
	    return (eval_sk_form(side, sk, arg1) >
		    eval_sk_form(side, sk, arg2));
	  case K_GE:
	    return (eval_sk_form(side, sk, arg1) >=
		    eval_sk_form(side, sk, arg2));
	  default:
	    run_warning("not a proper test");
	    return FALSE;
	}
    } else if (symbolp(form)) {
    	eval_symbol(form);
    }
    return FALSE;
}

/* Test whether a single side is still alive in the game. */

static void
eval_sk_last_side_wins(Scorekeeper *sk)
{
    Side *side2, *winner = NULL;
    int numleft = 0, points;

    /* This is only meaningful in games with at least two sides. */
    if (num_sides_originally < 2)
      return;
    for_all_sides(side2) {
	/* The independent side should not be counted among the sides
	   left (unless it has somebody running it), otherwise the
	   but-I-already-won!  player has to scour the map looking for
	   remaining inert independents. */
	if (inactive_indepside(side2))
	  continue;
	if (side2->ingame) {
	    points = side_point_value(side2);
	    Dprintf("%s has %d points worth of units\n",
		    side_desig(side2), points);
	    if (points == 0) {
		if (!only_checking)
		  side_loses(side2, NULL, sk->id);
	    } else {
		++numleft;
		/* Take note of the possible winner. */
		winner = side2;
	    }
	}
    }
    if (numleft == 1) {
	we_have_a_winner = TRUE;
	if (!only_checking)
	  side_wins(winner, sk->id);
    }
}

/* Test whether a single alliance (a group of trusting sides) is all
   that remains in the game. */

static void
eval_sk_last_alliance_wins(Scorekeeper *sk)
{
    Side *side2, *side3, *winner = NULL;
    int numleft = 0, sidepoints[MAXSIDES+1], alliancepoints[MAXSIDES+1];
    int alliancewins;

    /* This is only meaningful in games with at least two sides. */
    if (num_sides_originally < 2)
      return;
    for_all_sides(side2) {
	sidepoints[side2->id] = alliancepoints[side2->id] = 0;
	if (side2->ingame) {
	    sidepoints[side2->id] = side_point_value(side2);
	    Dprintf("%s has %d points worth of units\n",
		    side_desig(side2), sidepoints[side2->id]);
	}
    }
    /* Sum up to get points for each alliance. */
    for_all_sides(side2) {
	if (side2->ingame) {
	    for_all_sides(side3) {
		if (side2 == side3 || trusted_side(side2, side3)) {
		    alliancepoints[side2->id] += sidepoints[side3->id];
		}
	    }
	}
    }
    /* Make all the sides belonging to point-less alliances lose now,
       and look for a non-losing side. */
    for_all_sides(side2) {
	if (inactive_indepside(side2))
	  continue;
	if (side2->ingame) {
	    if (alliancepoints[side2->id] == 0) {
		if (!only_checking)
		  side_loses(side2, NULL, sk->id);
	    } else {
		++numleft;
		/* We still need to pick a single side, we'll take
		   care of all its buddies shortly. */
		winner = side2;
	    }
	}
    }
    /* It may happen that all sides lose (for instance if the last two
       units die in a duel). */
    if (numleft == 0)
      return;
    /* See if the non-losers all belong to a single alliance. */
    alliancewins = TRUE;
    for_all_sides(side2) {
	if (inactive_indepside(side2))
	  continue;
	if (side2->ingame
	    && side2 != indepside
	    && side2 != winner
	    && !trusted_side(winner, side2)) {
	    alliancewins = FALSE;
	    break;
	}
    }
    /* If so, everybody in the alliance wins equally. */
    if (alliancewins) {
	we_have_a_winner = TRUE;
	for_all_sides(side2) {
	    if (inactive_indepside(side2))
	      continue;
	    if (side2->ingame
		&& (side2 == winner || trusted_side(winner, side2))) {
		if (!only_checking)
		  side_wins(side2, sk->id);
	    }
	}
    }
}

/* Implement the effects of a side winning. */

void
side_wins(Side *side, int why)
{
    /* Nothing happens to the side's units or people. */
    side->status = 1;
    remove_side_from_game(side);
    /* Record the event after the side is removed, so we don't get infinite
       recursion if there is an event-triggered scorekeeper. */
    record_event(H_SIDE_WON, ALLSIDES, side_number(side), why);
}

/* Implement the effects of a side losing. */

void
side_loses(Side *side, Side *side2, int why)
{
    int x, y, s, s2, ux, uy, changed;
    Unit *unit;

    /* The independents cannot lose by definition even if they
    are down to zero points (unless played by a human). */
    if (side == indepside && !side_has_display(side))
      return;
    /* These should not happen, but a stupid AI might try, so just
       and clear the mistaken side. */
    if (side == side2) {
	run_warning("losing to self, ignoring side");
	side2 = NULL;
    }
    if (side2 != NULL && !side2->ingame) {
	run_warning("losing to side not in game, ignoring side");
	side2 = NULL;
    }
    /* If there's a controlling side, it gets everything. */
    if (side->controlled_by != NULL && side->controlled_by->ingame) {
	side2 = side->controlled_by;
    }
    if (side2 != NULL) {
	/* Dispose of all of a side's units. */
	for_all_units(unit) {
	    if (unit->side == side) {
		if (in_play(unit)) {
		    ux = unit->x;  uy = unit->y;
		    change_unit_side(unit, side2, H_SIDE_LOST, NULL);
		    /* Everybody gets to see this change. */
		    all_see_cell(ux, uy);
		} else {
		    /* Even out-of-play units need to have their side set. */
		    set_unit_side(unit, side2);
		}
	    }
	}
	/* The people also change sides. */
	if (people_sides_defined() || control_sides_defined()) {
	    s = side_number(side);
	    s2 = side_number(side2);
	    for_all_cells(x, y) {
		changed = FALSE;
		if (people_sides_defined() && people_side_at(x, y) == s) {
		    set_people_side_at(x, y, s2);
		    changed = TRUE;
		}
		if (control_sides_defined() && control_side_at(x, y) == s) {
		    set_control_side_at(x, y, s2);
		    changed = TRUE;
		}
		if (changed)
		  all_see_cell(x, y);
	    }
	}
	/* Reset view coverage everywhere. */
	if (!side->see_all) {
	    for_all_cells(x, y) {
		set_cover(side, x, y, 0);
	    }
	}
    }
    
    /* Advanced units (cities) should not disappear - make them independent. */
    else {
	for_all_units(unit) {
	    if (unit->side == side && u_advanced(unit->type)) {
		if (in_play(unit)) {
		    ux = unit->x;  uy = unit->y;
		    change_unit_side(unit, indepside, H_SIDE_LOST, NULL);
		    /* Everybody gets to see this change. */
		    all_see_cell(ux, uy);
		} else {
		    /* Even out-of-play units need to have their side set. */
		    set_unit_side(unit, indepside);
		}
	    }
	}
    }
    
    /* Add the mark of shame itself. */
    side->status = -1;
    remove_side_from_game(side);
    /* Record the event after the side is removed, so we don't get infinite
       recursion if there is an event-triggered scorekeeper. */
    record_event(H_SIDE_LOST, ALLSIDES, side_number(side), why);
    /* As a special case, look at post-turn scorekeepers to see if
       sides should end their turns early because the game is over. */
    only_checking = TRUE;
    check_post_turn_scores();
    only_checking = FALSE;
    if (we_have_a_winner) {
	for_all_sides(side2) {
	    if (side2->ingame) {
		finish_turn(side2);
	    }
	}
    }
    /* When the remaining sides' turns are finished, the end-of-turn
       processing will commence, and it includes the real run of
       post-turn scorekeeper checking. */
}

/* Implement a draw.  Note that unlike winning or losing, the draw
   applies to all sides currently in the game. */

void
all_sides_draw(void)
{
    SideMask drew;
    Side *side;

    drew = NOSIDES;
    for_all_sides(side) {
    	if (side->ingame) {
	    side->status = 0;
	    remove_side_from_game(side);
	    drew = add_side_to_set(side, drew);
	}
    }
    /* Now that all sides are out, safe to record events to that effect
       (no possibility of triggering post-event scorekeepers). */
    for_all_sides(side) {
    	if (side_in_set(side, drew)) {
	    record_event(H_SIDE_WITHDREW, ALLSIDES, side_number(side));
	}
    }
}

/* (should move this to nlang.c?) */
static char *
basic_player_name(Player *player)
{
    char *playername;

    playername = "";
    if (player) {
	if (!empty_string(player->name))
	  playername = player->name;
	else if (!empty_string(player->displayname))
	  playername = player->displayname;
	else if (!empty_string(player->aitypename))
	  playername = player->aitypename;
    }
    return playername;
}

/* Record the outcome of the game into the scorefile. */

void
record_into_scorefile(void)
{
    int i;
    int any_advantage_variation = FALSE, adv, adv2, advantage;
    char *filename, *mversion, *playername, *varname;
    Variant *variants, *var;
    FILE *fp;
    Side *side;

    /* (should make following code into a separate routine) */
    adv = -1;
    for_all_sides(side) {
	adv2 = actual_advantage(side);
	if (adv < 1)
	  adv = adv2;
	if (adv != adv2) {
	    any_advantage_variation = TRUE;
	    break;
	}
    }
    filename = SCOREFILE;
    if (!empty_string(g_scorefile_name()))
      filename = g_scorefile_name();
    fp = open_scorefile_for_writing(filename);
    if (fp == NULL) {
	run_warning("%s cannot be opened for writing, will not record score",
		    filename);
	/* (should provide some sort of retry here) */
    } else {
	fprintf(fp, "(g %s",
		/* (should record this for comparison when displaying
                   scores) */
		escaped_symbol((mainmodule->origmodulename
				? mainmodule->origmodulename
				: mainmodule->name)));
	/* Record the module's version if defined. */
	mversion = (mainmodule->origversion
		    ? mainmodule->origversion
		    : mainmodule->version);
	if (!empty_string(mversion))
	  fprintf(fp, " (ve \"%s\")", mversion);
	/* Record all the choices of variant. */
	variants = (mainmodule->origvariants
		    ? mainmodule->origvariants
		    : mainmodule->variants);
	if (variants) {
	    fprintf(fp, " (v");
	    for (i = 0; variants[i].id != lispnil; ++i) {
		var = &(variants[i]);
		varname = c_string(var->id);
		fprintf(fp, " (");
		/* Encode the common variants by number, for
		   compactness.  Note that once assigned, these
		   numbers can never change. */
		switch (keyword_code(varname)) {
		  case K_WORLD_SEEN:
		    fprintf(fp, "1 %d", var->intvalue);
		    break;
		  case K_SEE_ALL:
		    fprintf(fp, "2 %d", var->intvalue);
		    break;
		  case K_SEQUENTIAL:
		    fprintf(fp, "3 %d", var->intvalue);
		    break;
		  case K_PEOPLE:
		    fprintf(fp, "4 %d", var->intvalue);
		    break;
		  case K_ECONOMY:
		    fprintf(fp, "5 %d", var->intvalue);
		    break;
		  case K_SUPPLY:
		    fprintf(fp, "6 %d", var->intvalue);
		    break;
		  case K_WORLD_SIZE:
		    fprintf(fp, "11 %d %d %d",
			    area.width, area.height, world.circumference);
		    break;
		  case K_REAL_TIME:
		    fprintf(fp, "12 %d %d %d",
			    g_rt_for_game(),
			    g_rt_per_side(),
			    g_rt_per_turn());
		    break;
		  default:
		    fprintf(fp, "%s", escaped_symbol(varname));
		    if (var->hasintvalue) {
			fprintf(fp, " %d", var->intvalue);
		    }
		    /* Variants with unknown types of data end up getting
		       recorded as just "(<var-name>)", which is OK. */
		}
		fprintf(fp, ")");
	    }
	    fprintf(fp, ")");
	}
	fprintf(fp, " (t %d)", g_turn());
	/* End of the first line of a score record. */
	fprintf(fp, "\n");
	/* Record all the participants and how they fared. */
	fprintf(fp, " (s");
	for_all_sides(side) {
	    /* Only record info about sides that actually participated. */
	    if (side->everingame) {
		playername = basic_player_name(side->player);
		/* If a side has no player, don't record it. */
		if (empty_string(playername))
		  continue;
		fprintf(fp, " (");
		fprintf(fp, "%s", escaped_symbol(playername));
		fprintf(fp, " %s",
			(side_won(side)
			 ? "won"
			 : (side_lost(side)
			    ? "lost"
			    : "drew")));
		/* (should write info about ai helping human players) */
		if (any_advantage_variation) {
		    advantage = actual_advantage(side);
		    if (advantage > 1)
		      fprintf(fp, " (a %d)", advantage); 
		}
		if (numscores > 0) {
		    fprintf(fp, " (sc");
		    for (i = 0; i < numscores; ++i) {
			fprintf(fp, " %d", side->scores[i]);
		    }
		    fprintf(fp, ")");
		}
		fprintf(fp, ")");
	    }
	}
	fprintf(fp, ")");
	/* (should record other useful info about game, such as
           date(s) played) */
	fprintf(fp, ")\n");
	close_scorefile_for_writing(fp);
    }
}

static int
read_scorefile(void)
{
    int startlineno = 1, endlineno = 1;
    int numrecs;
    char *filename;
    Obj *form;
    FILE *fp;
    ScoreRecord *sr;

    filename = SCOREFILE;
    if (!empty_string(g_scorefile_name()))
      filename = g_scorefile_name();
    fp = open_scorefile_for_reading(filename);
    if (fp != NULL) {
        /* Note that we clear all existing score records.  This is because
	   the score file may be shared and thus getting multiple updates. */
	/* (should free any existing records) */
	records = NULL;
	numrecs = 0;
	/* Read everything in the file. */
	while ((form = read_form(fp, &startlineno, &endlineno)) != lispeof) {
	    if (interp_score_record(form)) {
		++numrecs;
	    }
	}
	fclose(fp);
	Dprintf("%d score records read.\n", numrecs);
	for_all_score_records(sr) {
	    Dprintf("%s\n", sr->gamename);
	}
	return TRUE;
    }
    return FALSE;
}

static int
interp_score_record(Obj *form)
{
    char *propname;
    Obj *props, *prop;
    ScoreRecord *sr;

    if (consp(form)
	&& symbolp(car(form))
	&& strcmp(c_string(car(form)), "g") == 0
	&& (stringp(cadr(form)) || symbolp(cadr(form)))) {
	sr = (ScoreRecord *) xmalloc(sizeof(ScoreRecord));
	sr->gamename = c_string(cadr(form));
	sr->sides = lispnil;
	sr->varsets = lispnil;
	for_all_list(cddr(form), props) {
	    prop = car(props);
	    if (symbolp(car(prop))) {
		propname = c_string(car(prop));
		if (strcmp(propname, "s") == 0) {
		    sr->sides = cdr(prop);
		} else if (strcmp(propname, "t") == 0) {
		    sr->numturns = c_number(cadr(prop));
		} else if (strcmp(propname, "v") == 0) {
		    sr->varsets = cdr(prop);
		} else if (strcmp(propname, "ve") == 0) {
		    /* (should do something with module version) */
		} else {
		    run_warning("Score record prop name `%s' not recognized, ignoring",
				propname);
		}
	    }
	}
	sr->raw = form;
	/* Add the record to the beginning of the list.  This is so displays list
	   the most recent game first. */
	sr->next = records;
	records = sr;
	return TRUE;
    } else {
	run_warning("Garbage in scorefile, ignoring");
    }
    return FALSE;
}

/* Collect scorefile contents and format them.  This routine starts from
   a fresh set of records each time, and allocates a new formatting
   buffer each time, so should be called sparingly. */

char *
get_scores(Side *side)
{
    int wins, losses, draws, plays, allplays;
    char *buf, sdadvbuf[20], varbuf[BUFSIZE];
    char *thisgame, *thisgametitle, *playername, *sdnamestr, *sdfatestr;
    Obj *sds, *sd, *sdname, *sdfate, *more, *more1;
    ScoreRecord *sr;

    thisgame = (mainmodule->origmodulename
		? mainmodule->origmodulename
		: mainmodule->name);
    if (thisgame == NULL)
      return "???";
    read_scorefile();
    if (records == NULL)
      return "No scores available.\n";
    buf = xmalloc(5000);
    thisgametitle = thisgame;
    if (!empty_string(mainmodule->title))
      thisgametitle = mainmodule->title;
    sprintf(buf, "Scores for %s", thisgametitle);
    strcat(buf, ":\n");
    playername = NULL;
    if (side != NULL) {
	playername = basic_player_name(side->player);
    }
    if (1 /* summarize */) {
	wins = losses = draws = plays = 0;
	allplays = 0;
	for_all_score_records(sr) {
	    if (!empty_string(sr->gamename)
		&& strcmp(thisgame, sr->gamename) == 0) {
		if (playername != NULL) {
		    /* Scan all the sides listed as having played in
                       the game. */
		    for_all_list(sr->sides, sds) {
			sd = car(sds);
			if (numberp(car(sd)))
			  sd = cdr(sd);
			sdname = car(sd);
			sdnamestr = ((symbolp(sdname) || stringp(sdname))
				     ? c_string(sdname) : "");
			sdfate = cadr(sd);
			sdfatestr = (symbolp(sdfate) ? c_string(sdfate) : "");
			if (sdnamestr != NULL
			    && strcmp(playername, sdnamestr) == 0) {
			    if (strcmp("won", sdfatestr) == 0) {
				++wins;
			    } else if (strcmp("lost", sdfatestr) == 0) {
				++losses;
			    } else if (strcmp("drew", sdfatestr) == 0) {
				++draws;
			    }
			    ++plays;
			    /* Only count the first appearance of the
			       player in the list; multiple
			       appearances can occur, but are likely
			       to be test games (two players with same
			       username open on the same X screen, for
			       instance) */
			    break;
			}
			/* (should study scores also?) */
		    }
		}
		++allplays;
	    }
	}
	/* (should go to nlang.c) */
	tprintf(buf, "You (%s) won %d, lost %d, and drew %d of %d game%s played.\n",
		playername, wins, losses, draws, plays, (plays == 1 ? "" : "s"));
	if (allplays != plays)
	  tprintf(buf, "Altogether, this game has been played %d time%s.\n",
		  allplays, (allplays == 1 ? "" : "s"));
	tprintf(buf, "\n\n");
    }
    /* List all the games explicitly. */
    if (1 /* complete listing */) {
	tprintf(buf, "Listing of games played:\n");
	for_all_score_records(sr) {
	    if (!empty_string(sr->gamename)
		&& strcmp(thisgame, sr->gamename) == 0) {
		if (playername == NULL
		    || 1 /* matching playername */) {
		    for_all_list(sr->sides, sds) {
			sd = car(sds);
			if (numberp(car(sd)))
			  sd = cdr(sd);
			sdname = car(sd);
			sdnamestr = ((symbolp(sdname) || stringp(sdname))
				     ? c_string(sdname) : "?");
			sdfate = cadr(sd);
			sdfatestr = (symbolp(sdfate) ? c_string(sdfate) : "?");
			strcpy(sdadvbuf, "");
			for_all_list(cddr(sd), more) {
			    more1 = car(more);
			    Dprintf("entry is");
			    Dprintlisp(more1);
			    Dprintf("\n");
			    if (consp(more1)
				&& symbolp(car(more1))
				&& strcmp(c_string(car(more1)), "a") == 0
				&& numberp(cadr(more1))) {
				sprintf(sdadvbuf, " +%d",
					c_number(cadr(more1)));
				break;
			    }
			}
			if (sds != sr->sides)
			  tprintf(buf, ", ");
			tprintf(buf, "%s%s %s",
				(!empty_string(sdnamestr)
				 ? sdnamestr : "(no player)"),
				sdadvbuf, sdfatestr);
		    }
		    if (sr->numturns > 0)
		      tprintf(buf, " (in %d turn%s)",
			      sr->numturns, (sr->numturns != 1 ? "s" : ""));
		    score_variant_desc(sr, varbuf);
		    if (!empty_string(varbuf)) {
			strcat(buf, " (variants");
			strcat(buf, varbuf);
			strcat(buf, ")");
		    }
		    tprintf(buf, "\n");
		    if (strlen(buf) > 4500)
		      break;
		}
	    }
	}
    }
    return buf;
}

/* Given a score record, report any variants that were specified and
   different from the current module's defaults. */

static void
score_variant_desc(ScoreRecord *sr, char *varbuf)
{
    int i, hasdflt, dfltval, val;
    Obj *restvarset, *varset, *varsetname, *rslt;
    Variant *variants, *tmpvar, *var;

    varbuf[0] = '\0';
    if (sr->varsets != lispnil) {
	variants = (mainmodule->origvariants
		    ? mainmodule->origvariants
		    : mainmodule->variants);
	for_all_list(sr->varsets, restvarset) {
	    varset = car(restvarset);
	    if (consp(varset)) {
		if (numberp(car(varset))) {
		    /* Get the names of the standard variants from
                       their numbers. */
		    switch (c_number(car(varset))) {
		      case 1:
			varsetname = intern_symbol(keyword_name(K_WORLD_SEEN));
			break;
		      case 2:
			varsetname = intern_symbol(keyword_name(K_SEE_ALL));
			break;
		      case 3:
			varsetname = intern_symbol(keyword_name(K_SEQUENTIAL));
			break;
		      case 4:
			varsetname = intern_symbol(keyword_name(K_PEOPLE));
			break;
		      case 5:
			varsetname = intern_symbol(keyword_name(K_ECONOMY));
			break;
		      case 6:
			varsetname = intern_symbol(keyword_name(K_SUPPLY));
			break;
		      case 11:
			varsetname = intern_symbol(keyword_name(K_WORLD_SIZE));
			break;
		      case 12:
			varsetname = intern_symbol(keyword_name(K_REAL_TIME));
			break;
		      default:
			break;
		    }
		} else {
		    varsetname = car(varset);
		}
		var = NULL;
		if (variants != NULL) {
		    for (i = 0; variants[i].id != lispnil; ++i) {
			tmpvar = &(variants[i]);
			if (tmpvar->id == varsetname) {
			    var = tmpvar;
			    break;
			}
		    }
		}
		hasdflt = dfltval = FALSE;
		/* Don't complain if variant not found, might have been
		   removed from current module. */
		if (var != NULL) {
		    rslt = eval(var->dflt);
		    if (numberp(rslt))
		      dfltval = c_number(rslt);
		    hasdflt = TRUE;
		}
		val = 54321;
		/* The numeric value is the second element in the variant
		   setting. */
		if (numberp(cadr(varset)) && cddr(varset) == lispnil)
		  val = c_number(cadr(varset));
		/* Handle variants with several values. */
		if (keyword_code(c_string(varsetname)) == K_WORLD_SIZE) {
		    if (!hasdflt || !equal(rslt, cdr(varset)))
		      tprintf(varbuf, " %s=%dx%dW%d", c_string(varsetname),
			      c_number(cadr(varset)), c_number(caddr(varset)),
			      c_number(cadr(cddr(varset))));
		} else if (keyword_code(c_string(varsetname)) == K_REAL_TIME) {
		    if (!hasdflt || !equal(rslt, cdr(varset)))
		      tprintf(varbuf, " %s=%d,%d,%d", c_string(varsetname),
			      c_number(cadr(varset)), c_number(caddr(varset)),
			      c_number(cadr(cddr(varset))));
		} else {
		    if (!hasdflt || val != dfltval)
		      tprintf(varbuf, " %s=%d", c_string(varsetname), val);
		}
	    }
	}
    }
}

/* This is a general test of whether the given side should be trying
   to win somehow, or if it can just goof off. */

int
should_try_to_win(Side *side)
{
    Scorekeeper *sk;

    for_all_scorekeepers(sk) {
	if (scorekeeper_applicable(side, sk)) {
	    return TRUE;
	}
    }
    return FALSE;
}
