/* Scores and scoring in Xconq.
   Copyright (C) 1987-1989, 1991-1998 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.  */

#include "conq.h"
extern char *escaped_symbol PARAMS ((char *str));
extern char *escaped_string PARAMS ((char *str));
#include "kernel.h"

char *basic_player_name PARAMS ((Player *player));
int read_scorefile PARAMS ((void));
int interp_score_record PARAMS ((Obj *form));
char *get_scores PARAMS ((Side *side));

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 void eval_sk_last_side_wins PARAMS ((Scorekeeper *sk));
static void eval_sk_last_alliance_wins PARAMS ((Scorekeeper *sk));

/* 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;

ScoreRecord *records;

/* Clear out any possible scorekeepers. */

void
init_scorekeepers()
{
    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()
{
    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(id)
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()
{
    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()
{
    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()
{
    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()
{
    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()
{
    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, sk)
Side *side;
Scorekeeper *sk;
{
    eval_sk_form(side, sk, sk->body);
}

int
eval_sk_form(side, 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)
	    side_wins(side, sk->id);
	  break;
	case K_LOSE:
	  if (side->ingame)
	    side_loses(side, NULL, sk->id);
	  break;
	case K_END:
	  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, form)
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 *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 *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, sk, form)
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;
}

static void
eval_sk_last_side_wins(sk)
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) {
	for_all_sides(side2) {
	    if (side2->ingame) {
		points = side_point_value(side2);
		Dprintf("%s has %d points worth of units\n",
			side_desig(side2), points);
		if (points == 0) {
		    side_loses(side2, NULL, sk->id);
		} else {
		    ++numleft;
		    winner = side2;
		}
	    }
	}
	if (numleft == 1) {
	    side_wins(winner, sk->id);
	}
    }
}

static void
eval_sk_last_alliance_wins(sk)
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) {
	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 (side2->ingame) {
		if (alliancepoints[side2->id] == 0) {
		    side_loses(side2, NULL, sk->id);
		} else {
		    ++numleft;
		    winner = side2;
		}
	    }
	}
	if (numleft > 0) {
	    /* See if the non-losers all belong to a single alliance. */
	    alliancewins = TRUE;
	    for_all_sides(side2) {
		if (side2->ingame && side2 != winner
		    && !trusted_side(winner, side2)) {
		    alliancewins = FALSE;
		    break;
		}
	    }
	    /* If so, everybody in the alliance wins equally. */
	    if (alliancewins) {
		for_all_sides(side2) {
		    if (side2->ingame
			&& (side2 == winner || trusted_side(winner, side2))) {
			side_wins(side2, sk->id);
		    }
		}
	    }
	}
    }
}

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

void
side_wins(side, why)
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, side2, why)
Side *side, *side2;
int why;
{
    int x, y, s, s2, ux, uy, changed;
    Unit *unit;

    /* 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 (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 (!all_see_all) {
	    for_all_cells(x, y) {
		set_cover(side, x, y, 0);
	    }
	}
    }
    
// --- ADVANCED UNITS (CITIES) SHOULD NOT JUST 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, NULL, 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, NULL);
		}
	    }
	}
    }
    
// ---------------------------------------------------------------------    
    
    /* 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);
}

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

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

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

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

char *
basic_player_name(player)
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;
}

void
record_into_scorefile()
{
    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 = max(1, side->advantage);
	if (side->player)
	  adv2 = side->player->advantage;
	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) {
	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. */
		if (keyword_code(varname) == K_WORLD_SEEN) {
		    fprintf(fp, "1 %d", var->intvalue);
		} else if (keyword_code(varname) == K_SEE_ALL) {
		    fprintf(fp, "2 %d", var->intvalue);
		} else if (keyword_code(varname) == K_SEQUENTIAL) {
		    fprintf(fp, "3 %d", var->intvalue);
		} else if (keyword_code(varname) == K_WORLD_SIZE) {
		    fprintf(fp, "4 %d %d %d",
			    area.width, area.height, world.circumference);
		} else if (keyword_code(varname) == K_REAL_TIME) {
		    fprintf(fp, "5 %d %d %d",
			    g_rt_for_game(),
			    g_rt_per_side(),
			    g_rt_per_turn());
		} else {
		    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) {
		fprintf(fp, " (");
		playername = basic_player_name(side->player);
		fprintf(fp, "%s", escaped_symbol(playername));
		fprintf(fp, " %s",
			(side_won(side)
			 ? "won"
			 : (side_lost(side)
			    ? "lost"
			    : "drew")));
		/* (should write info about ai helpers) */
		if (any_advantage_variation) {
		    /* (should have cached as "actual advantage"?) */
		    advantage = max(1, side->advantage);
		    if (side->player)
		      advantage = side->player->advantage;
		    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);
    }
}

int
read_scorefile()
{
    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) {
	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;
}

int
interp_score_record(form)
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;
}

static void score_variant_desc PARAMS ((ScoreRecord *sr, char *varbuf));

char *
get_scores(side)
Side *side;
{
    int wins, losses, draws, plays, allplays, i;
    char *buf, buf2[BUFSIZE], sdadvbuf[20], varbuf[BUFSIZE];
    char *thisgame, *playername, *sdnamestr, *sdfatestr;
    Obj *sds, *sd, *sdname, *sdfate, *more, *more1, *restvarset, *varset;
    Variant *variants, *tmpvar, *var;
    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);
    sprintf(buf, "Scores for \"%s\":\n", thisgame);
    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(sr, varbuf)
ScoreRecord *sr;
char *varbuf;
{
    int i, hasdflt, dfltval, num, 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_WORLD_SIZE));
			break;
		      case 5:
			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;
		if (numberp(cadr(varset)) && cddr(varset) == lispnil)
		  val = c_number(cadr(varset));
		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 *side;
{
    Scorekeeper *sk;

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