/* Toplevel code for the tcl/tk interface to Xconq.
   Copyright (C) 1998, 1999 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.  */

#include "conq.h"
extern void notify_instructions(void);
extern int sendnow;
extern int num_features(void);
extern void flush_outgoing_queue(void);
#include "kpublic.h"
#include "tkconq.h"


extern void host_the_game(char *option);
extern void try_join_game(char *option);
extern void init_all_displays(void);

void draw_unit_info(Map *map);

void place_legends(Side *side);

extern int connection_method;

extern Tk_Window tmp_root_window;
extern int tmp_valid;

/* We only need one tcl interpreter, and this is it. */

Tcl_Interp *interp;

/* The one side that has a display in this program. */

Side *dside;

int lastrawx, lastrawy;

/* Storage for values of user preferences. */

VP default_vp;
int default_draw_terrain_images;
int default_draw_terrain_patterns;
int default_draw_transitions;
char *default_font_family;
int default_font_size;

int tk_version_string(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[]);
int tk_copyright_string(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[]);
int tk_run_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[]);
int tk_run_game_idle(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int tk_animate_selection(ClientData cldata, Tcl_Interp *interp,
			 int argc, char *argv[]);
int tk_interp_key(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[]);
int tk_execute_long_command(ClientData cldata, Tcl_Interp *interp,
			    int argc, char *argv[]);
int tk_library_paths(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int tk_numgames(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[]);
int tk_interpret_variants(ClientData cldata, Tcl_Interp *interp,
			  int argc, char *argv[]);
int tk_set_variant_value(ClientData cldata, Tcl_Interp *interp,
			  int argc, char *argv[]);
int tk_implement_variants(ClientData cldata, Tcl_Interp *interp,
			  int argc, char *argv[]);
int tk_numttypes(ClientData cldata, Tcl_Interp *interp,
		 int argc, char *argv[]);
int tk_numutypes(ClientData cldata, Tcl_Interp *interp,
		 int argc, char *argv[]);
int tk_numsides(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[]);
int tk_maxsides(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[]);
int tk_dside(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[]);
int tk_numtreasury(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[]);
int tk_numfeatures(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[]);
int tk_game_info(ClientData cldata, Tcl_Interp *interp,
		 int argc, char *argv[]);
int tk_ttype_name(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[]);
int tk_t_image_name(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[]);
int tk_utype_name(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[]);
int tk_u_image_name(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[]);
int tk_mtype_name(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[]);
int tk_side_name(ClientData cldata, Tcl_Interp *interp,
		 int argc, char *argv[]);
int tk_side_adjective(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[]);
int tk_side_emblem(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[]);
int tk_short_side_title(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[]);
int tk_side_ingame(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[]);
int tk_long_player_title(ClientData cldata, Tcl_Interp *interp,
			 int argc, char *argv[]);
int tk_player_advantage(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[]);
int tk_player_remote_status(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[]);
int tk_min_advantage(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int tk_max_advantage(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int tk_assigned_side(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int tk_assigned_player(ClientData cldata, Tcl_Interp *interp,
		       int argc, char *argv[]);
int tk_can_rename(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[]);
int tk_adjust_advantage(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[]);
int tk_add_side_and_player(ClientData cldata, Tcl_Interp *interp,
			   int argc, char *argv[]);
int tk_rename_side_for_player(ClientData cldata, Tcl_Interp *interp,
			      int argc, char *argv[]);
int tk_set_ai_for_player(ClientData cldata, Tcl_Interp *interp,
			 int argc, char *argv[]);
int tk_set_remote_for_player(ClientData cldata, Tcl_Interp *interp,
			     int argc, char *argv[]);
int tk_exchange_players(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[]);
int tk_feature_name(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[]);
int tk_set_feature_name(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[]);
int tk_start_new_game(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[]);
int tk_launch_game(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[]);
int tk_launch_game_2(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int tk_try_join_game(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int tk_set_unit_type(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int tk_mouse_down_cmd(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[]);
int tk_mouse_up_cmd(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[]);
int tk_mouse_over_cmd(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[]);
int tk_world_mouse_down_cmd(ClientData cldata, Tcl_Interp *interp,
			    int argc, char *argv[]);
int tk_world_mouse_up_cmd(ClientData cldata, Tcl_Interp *interp,
			  int argc, char *argv[]);
int tk_save_prefs(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[]);
int tk_help_goto(ClientData cldata, Tcl_Interp *interp,
		 int argc, char *argv[]);
int tk_set_design_tool(ClientData cldata, Tcl_Interp *interp,
		       int argc, char *argv[]);
int tk_set_design_data(ClientData cldata, Tcl_Interp *interp,
		       int argc, char *argv[]);
int tk_create_new_feature(ClientData cldata, Tcl_Interp *interp,
			  int argc, char *argv[]);
int tk_designer_save(ClientData cldata, Tcl_Interp *interp,
		     int argc, char *argv[]);
int mapw_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[]);
int imfsample_cmd(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[]);

int tk_numutypes_available(ClientData cldata, Tcl_Interp *interp,
			   int argc, char *argv[]);
int tk_utype_actual(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[]);
int tk_mtype_actual(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[]);
int tk_map_size_at_power(ClientData cldata, Tcl_Interp *interp,
			 int argc, char *argv[]);

int tk_agreements(ClientData cldata, Tcl_Interp *interp,
		  int argc, char *argv[]);

void update_mouseover(Map *map, int rawx, int rawy);

/* Declarations of local functions. */

static void update_side_progress_display(Side *side, Side *side2);
static void update_side_score_display(Side *side, Side *side2);

static void interpret_variants(void);
static void set_variant_value(int which, int val);
static void set_variant_world_size(int wid, int hgt, int circumf, int lat,
				   int lon);
static void set_variant_real_time(int total, int perside, int perturn);
static void implement_variants(void);

static void help_unit_type(Side *side, Map *map);
static void help_terrain_type(Side *side, Map *map);

static void init_unit_type_list(int u);
static void update_unit_type_list(int u);

static void ui_update_state(void);

/* Create the one global interpreter, add Xconq-specific commands to it. */

void
initial_ui_init(void)
{
    char pathbuf[500];
    int rslt;

    Tk_Window tkwin;

    Tcl_FindExecutable("xconq");

    interp = Tcl_CreateInterp();

    if (Tcl_Init(interp) == TCL_ERROR) {
	init_error("tcl init failed (%s), exiting", interp->result);
    }

    if (Tk_Init(interp) == TCL_ERROR) {
	init_error("tk init failed (%s), exiting", interp->result);
    }

    Tcl_CreateCommand(interp, "version_string", tk_version_string,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "copyright_string", tk_copyright_string,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "run_game", tk_run_game,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "run_game_idle", tk_run_game_idle,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "animate_selection", tk_animate_selection,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "interp_key", tk_interp_key,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "execute_long_command", tk_execute_long_command,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "xconq_library_paths", tk_library_paths,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "numgames", tk_numgames,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "interpret_variants", tk_interpret_variants,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "set_variant_value", tk_set_variant_value,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "implement_variants", tk_implement_variants,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "numttypes", tk_numttypes,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "numutypes", tk_numutypes,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "numsides", tk_numsides,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "maxsides", tk_maxsides,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "numtreasury", tk_numtreasury,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "numfeatures", tk_numfeatures,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "game_info", tk_game_info,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "ttype_name", tk_ttype_name,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "t_image_name", tk_t_image_name,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "utype_name", tk_utype_name,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "u_image_name", tk_u_image_name,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "mtype_name", tk_mtype_name,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "side_name", tk_side_name,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "side_adjective", tk_side_adjective,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "side_emblem", tk_side_emblem,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "short_side_title", tk_short_side_title,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "side_ingame", tk_side_ingame,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "long_player_title", tk_long_player_title,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "player_advantage", tk_player_advantage,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "player_remote_status", tk_player_remote_status,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "min_advantage", tk_min_advantage,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "max_advantage", tk_max_advantage,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "assigned_side", tk_assigned_side,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "assigned_player", tk_assigned_player,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "can_rename", tk_can_rename,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "adjust_advantage", tk_adjust_advantage,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "add_side_and_player", tk_add_side_and_player,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "rename_side_for_player", tk_rename_side_for_player,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "set_ai_for_player", tk_set_ai_for_player,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "set_remote_for_player", tk_set_remote_for_player,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "exchange_players", tk_exchange_players,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "dside", tk_dside,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "feature_name", tk_feature_name,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "set_feature_name", tk_set_feature_name,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "start_new_game", tk_start_new_game,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "launch_game", tk_launch_game,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "launch_game_2", tk_launch_game_2,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "try_join_game", tk_try_join_game,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "set_unit_type", tk_set_unit_type,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "mouse_down_cmd", tk_mouse_down_cmd,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "mouse_up_cmd", tk_mouse_up_cmd,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "mouse_over_cmd", tk_mouse_over_cmd,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "world_mouse_down_cmd", tk_world_mouse_down_cmd,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "world_mouse_up_cmd", tk_world_mouse_up_cmd,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "save_preferences", tk_save_prefs,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "help_goto", tk_help_goto,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "set_design_tool", tk_set_design_tool,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "set_design_data", tk_set_design_data,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "create_new_feature", tk_create_new_feature,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "designer_save", tk_designer_save,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "numutypes_available", tk_numutypes_available,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "utype_actual", tk_utype_actual,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);
    Tcl_CreateCommand(interp, "mtype_actual", tk_mtype_actual,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "map_size_at_power", tk_map_size_at_power,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "agreements", tk_agreements,
		      (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

    tkwin = Tk_MainWindow(interp);

    Tcl_CreateCommand(interp, "map", mapw_cmd,
		      (ClientData) tkwin, (Tcl_CmdDeleteProc *) NULL);

    Tcl_CreateCommand(interp, "imfsample", imfsample_cmd,
		      (ClientData) tkwin, (Tcl_CmdDeleteProc *) NULL);

    {
	int loaded = FALSE;
	LibraryPath *p;
	FILE *fp;

	for_all_library_paths(p) {
	    make_pathname(p->path, "../tcltk/tkconq", "tcl", pathbuf);
	    if ((fp = fopen(pathbuf, "r")) != NULL) {
		fclose(fp);
		rslt = Tcl_EvalFile(interp, pathbuf);
		if (rslt == TCL_ERROR)
		  init_error("Error reading tcl: %s", interp->result);
		loaded = TRUE;
		break;
	    }
	    make_pathname(p->path, "../tkconq", "tcl", pathbuf);
	    if ((fp = fopen(pathbuf, "r")) != NULL) {
		fclose(fp);
		rslt = Tcl_EvalFile(interp, pathbuf);
		if (rslt == TCL_ERROR)
		  init_error("Error reading tcl: %s", interp->result);
		loaded = TRUE;
		break;
	    }
	}
	/* (should list unsuccessful paths) */
	if (!loaded)
	  init_error("tkconq.tcl file could not be loaded");
    }

    imf_interp_hook = tk_interp_imf;
    imf_load_hook = tk_load_imf;
}

void
ui_init(void)
{
    Side *side;

    eval_tcl_cmd("do_initial_setup");

    for_all_sides(side) {
	if (side->ui)
	  dside = side;
    }

    /* cache the highest feature number; don't forget to update this 
       if new featured are added (presently not implemented)  */
    dside->ui->numfeatures = num_features();
    place_legends(dside);
}

int
tk_library_paths(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char buf[500];
    LibraryPath *path;

    strcpy(buf, "");
    for_all_library_paths(path) {
	/* Use semicolons as separators, for the benefit of split. */
	/* (It would be clever to to use a char known not to be in any
	   of the paths, but only do that much work when it proves
	   necessary). */
	if (!empty_string(buf))
	  strcat(buf, ";");
	strcat(buf, path->path);
    }
    sprintf(interp->result, "%s", buf);
    return TCL_OK;
}

int
tk_numgames(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    collect_possible_games();
    sprintf(interp->result, "%d", numgames);
    return TCL_OK;
}

int
tk_interpret_variants(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    interpret_variants();
    return TCL_OK;
}

int
tk_set_variant_value(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int which, val;

    which = strtol(argv[1], NULL, 10);
    if (which == -1) {
	set_variant_world_size(strtol(argv[2], NULL, 10),
			       strtol(argv[3], NULL, 10),
			       strtol(argv[4], NULL, 10),
			       strtol(argv[5], NULL, 10),
			       strtol(argv[6], NULL, 10));
    } else if (which == -2) {
	set_variant_real_time(strtol(argv[2], NULL, 10),
			      strtol(argv[3], NULL, 10),
			      strtol(argv[4], NULL, 10));
    } else {
	val = strtol(argv[2], NULL, 10);
	set_variant_value(which, val);
    }
    return TCL_OK;
}

int
tk_implement_variants(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    implement_variants();
    return TCL_OK;
}

int
tk_numttypes(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", numttypes);
    return TCL_OK;
}

int
tk_numutypes(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", numutypes);
    return TCL_OK;
}

int
tk_numsides(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", numsides);
    return TCL_OK;
}

int
tk_maxsides(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", g_sides_max());
    return TCL_OK;
}

int
tk_numfeatures(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", num_features());
    return TCL_OK;
}

int
tk_numtreasury(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int m, rslt = 0;

    for_all_material_types(m) {
	if (m_treasury(m))
	  ++rslt;
    }
    sprintf(interp->result, "%d", rslt);
    return TCL_OK;
}

int
tk_ttype_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int t;

    t = strtol(argv[1], NULL, 10);
    if (is_terrain_type(t))
      sprintf(interp->result, "%s", t_type_name(t));
    else
      sprintf(interp->result, "?t?");
    return TCL_OK;
}

int
tk_t_image_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int t;
    char *str;

    t = strtol(argv[1], NULL, 10);
    if (is_terrain_type(t)) {
	str = t_image_name(t);
	if (empty_string(str))
	  str = t_type_name(t);
	sprintf(interp->result, "%s", str);
    } else
      sprintf(interp->result, "?t?");
    return TCL_OK;
}

int
tk_utype_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int u;

    u = strtol(argv[1], NULL, 10);
    if (is_unit_type(u))
      sprintf(interp->result, "%s", u_type_name(u));
    else
      sprintf(interp->result, "?u?");
    return TCL_OK;
}

int
tk_u_image_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int u;
    char *str;

    u = strtol(argv[1], NULL, 10);
    if (is_unit_type(u)) {
	str = u_image_name(u);
	if (empty_string(str))
	  str = u_internal_name(u);
	sprintf(interp->result, "%s", str);
    } else
      sprintf(interp->result, "?u?");
    return TCL_OK;
}

int
tk_mtype_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int m;

    m = strtol(argv[1], NULL, 10);
    if (is_material_type(m))
      sprintf(interp->result, "%s", m_type_name(m));
    else
      sprintf(interp->result, "?m?");
    return TCL_OK;
}

int
tk_side_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (between(0, s, numsides))
      sprintf(interp->result, "%s", side_name(side_n(s)));
    else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_side_adjective(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (between(0, s, numsides))
      sprintf(interp->result, "%s", side_adjective(side_n(s)));
    else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_side_emblem(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (s == 0)
      sprintf(interp->result, indepside->emblemname);
    else if (between(1, s, numsides)) {
	/* Try to get the emblem that was actually set up - it may be
	   a default or something else different from the official
	   emblem name. */
	if (dside && dside->ui && dside->ui->eimages[s])
	  sprintf(interp->result, "%s", dside->ui->eimages[s]->name);
	else if (side_n(s)->emblemname)
	  sprintf(interp->result, "%s", (side_n(s))->emblemname);
	else
	  sprintf(interp->result, "null");
    } else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_short_side_title(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (between(0, s, numsides))
      /* (should handle empty string?) */
      sprintf(interp->result, "%s", short_side_title(side_n(s)));
    else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_side_ingame(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;

    s = strtol(argv[1], NULL, 10);
    if (between(1, s, numsides))
      sprintf(interp->result, "%d", side_n(s)->ingame);
    else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_long_player_title(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int p;
    char abuf[300];
    Player *player;

    p = strtol(argv[1], NULL, 10);
    player = find_player(p);
    long_player_title(abuf, player, NULL);
    sprintf(interp->result, "%s", abuf);
    return TCL_OK;
}

int
tk_player_advantage(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int p;
    Player *player;

    p = strtol(argv[1], NULL, 10);
    player = find_player(p);
    sprintf(interp->result, "%d", (player ? player->advantage : 0));
    return TCL_OK;
}

int
tk_player_remote_status(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int p;
    Player *player;

    p = strtol(argv[1], NULL, 10);
    player = find_player(p);
    sprintf(interp->result, "%s",
	    ((player != NULL && player->remotewanted)
	     ? (player->rid > 0 ? "R" : "r")
	     : "-"));
    return TCL_OK;
}

int
tk_min_advantage(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;
    Side *side;

    s = strtol(argv[1], NULL, 10);
    side = side_n(s);
    sprintf(interp->result, "%d", (side ? side->minadvantage : 0));
    return TCL_OK;
}

int
tk_max_advantage(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;
    Side *side;

    s = strtol(argv[1], NULL, 10);
    side = side_n(s);
    sprintf(interp->result, "%d", (side ? side->maxadvantage : 0));
    return TCL_OK;
}

int
tk_assigned_side(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int i;
    Side *side;

    i = strtol(argv[1], NULL, 10);
    if (between(0, i, numsides)) {
	side = assignments[i].side;
	sprintf(interp->result, "%d", side_number(side));
    } else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_assigned_player(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int i;
    Player *player;

    i = strtol(argv[1], NULL, 10);
    if (between(0, i, numsides)) {
	player = assignments[i].player;
	sprintf(interp->result, "%d", (player ? player->id : 0));
    } else
      sprintf(interp->result, "?s?");
    return TCL_OK;
}

int
tk_can_rename(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int s;
    Side *side;

    s = strtol(argv[1], NULL, 10);
    side = side_n(s);
    sprintf(interp->result, "%d",
	    g_side_lib() != lispnil && side != NULL && !side->nameslocked);
    return TCL_OK;
}

int
tk_adjust_advantage(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int p, amt;
    Player *player;

    p = strtol(argv[1], NULL, 10);
    amt = strtol(argv[2], NULL, 10);
    player = assignments[p].player;
    player->advantage += amt;
    return TCL_OK;
}

int
tk_add_side_and_player(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    add_side_and_player();
    /* Return the position of the new side/player in the assignment array. */
    sprintf(interp->result, "%d", numsides - 1);
    return TCL_OK;
}

int
tk_rename_side_for_player(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int n;
    Side *side;

    n = strtol(argv[1], NULL, 10);
    side = assignments[n].side;
    side->name = side->noun = side->pluralnoun = side->adjective = NULL;
    make_up_side_name(side);
    return TCL_OK;
}

int
tk_set_ai_for_player(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int n;
    char *aitype, *mode;
    Player *player;

    n = strtol(argv[1], NULL, 10);
    aitype = copy_string(argv[2]);
    mode = argv[3];
    player = assignments[n].player;
    if (strcmp(mode, "toggle") == 0) {
	if (player->aitypename)
	  player->aitypename = NULL;
	else
	  player->aitypename = aitype;
    }
    /* (should add other cases eventually) */
    return TCL_OK;
}

int
tk_set_remote_for_player(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int n;
    char *mode;
    Player *player;
    extern int hosting;

    n = strtol(argv[1], NULL, 10);
    mode = argv[2];
    player = assignments[n].player;
    if (strcmp(mode, "toggle") == 0) {
	if (player->remotewanted)
	  player->remotewanted = NULL;
	else
	  player->remotewanted = "x";	
    }
    if (player->remotewanted && !hosting) {
	host_the_game(":3075");
    }
    return TCL_OK;
}

int
tk_exchange_players(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int n, n2;

    n = strtol(argv[1], NULL, 10);
    n2 = strtol(argv[2], NULL, 10);
    if (between(0, n, numsides)) {
	n2 = exchange_players(n, n2);
	sprintf(interp->result, "%d", n2);
    } else {
	sprintf(interp->result, "?s?");
    }
    return TCL_OK;
}

int
tk_dside(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    sprintf(interp->result, "%d", dside->id);
    return TCL_OK;
}

int
tk_feature_name(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int fid;
    Feature *feature;

    fid = strtol(argv[1], NULL, 10);
    feature = find_feature(fid);
    if (feature && feature->name)
      sprintf(interp->result, "%s", feature->name);
    else
      sprintf(interp->result, "?f?");
    return TCL_OK;
}

int
tk_set_feature_name(ClientData cldata, Tcl_Interp *interp,
		    int argc, char *argv[])
{
    int fid;
    char *fname;
    Feature *feature;

    fid = strtol(argv[1], NULL, 10);
    feature = find_feature(fid);
    fname = argv[2];
    if (feature != NULL)
      net_set_feature_name(feature, fname);
    return TCL_OK;
}

int
tk_version_string(ClientData cldata, Tcl_Interp *interp, int argc,
		  char *argv[])
{
    sprintf(interp->result, "%s", version_string());
    return TCL_OK;
}

int
tk_copyright_string(ClientData cldata, Tcl_Interp *interp, int argc,
		  char *argv[])
{
    sprintf(interp->result, "%s", copyright_string());
    return TCL_OK;
}

int
tk_game_info(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int gamei;
    char *name, *title, *blurb, *bname;

    gamei = strtol(argv[1], NULL, 10);
    if (possible_games[gamei] != NULL) {
	name = possible_games[gamei]->name;
	title = possible_games[gamei]->title;
	if (title == NULL)
	  title = "";
	blurb = possible_games[gamei]->blurb;
	if (blurb == NULL)
	  blurb = "";
	if (empty_string(blurb))
	  blurb = "(no description)";
	bname = (possible_games[gamei]->basemodulename ? "-  " : "");
    } else {
	name = title = blurb = bname = "";
    }
    sprintf(interp->result, "{\"%s\" \"%s\" \"%s\" \"%s\"}",
	    name, title, blurb, bname);
    return TCL_OK;
}

int
tk_start_new_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int gamei;

    gamei = strtol(argv[1], NULL, 10);
    mainmodule = possible_games[gamei];
    load_game_module(mainmodule, TRUE);
    check_game_validity();
    return TCL_OK;
}

int
tk_launch_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    make_trial_assignments();
    return TCL_OK;
}

int
tk_launch_game_2(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    launch_game();
    ui_init();
    /* Get the displays set up, but don't draw anything yet. */
    init_all_displays();
    /* Now bring up the init data on each display. */
    init_redraws();
    /* Set up the signal handlers. */
    init_signal_handlers();
    /*    notify_all("Command was \"%s %s\"", argv[0], args_used); */
    notify_instructions();
    return TCL_OK;
}

int
tk_try_join_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    try_join_game(argv[1]);
    return TCL_OK;
}

int
tk_run_game(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int maxactions, rslt = 0, interval;

    if (argc != 2) {
	run_warning("wrong # args (%d) to run_game", argc - 1);
	return TCL_OK;
    }

    maxactions = strtol(argv[1], NULL, 10);

    /* Check for any input from remotes. */
    if (connection_method > 0 && my_rid > 0) {
	flush_outgoing_queue();
	receive_data(0);
	++rslt;
    }
    run_local_ai(1, 20);
    /* Run the kernel itself. */
    rslt += net_run_game(maxactions);
    run_local_ai(2, 20);
    /* Check for any input from remotes. */
    if (connection_method > 0 && my_rid > 0) {
	flush_outgoing_queue();
	receive_data(0);
	++rslt;
    }
    /* Set up to call it again in a little while. */
    /* If things are happening, call 40 times/sec, for responsiveness. */
    interval = 25;
    /* If nothing is happening right now, do at 4 times/sec. */
    if (rslt == 0)
      interval = 250;
    sprintf(interp->result, "%d", interval);
    return TCL_OK;
}

int
tk_run_game_idle(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    Map *map;
    Unit *unit, *unit2;
    char *activity = "tk_idle";

    if (!active_display(dside))
      return TCL_OK;
    record_activity_start(activity, 0);
    /* See if we should jump to another unit and make it current. */
    for_all_maps(map) {
	if (map->widget == NULL)
	  continue;
	unit = map->curunit;
	if (map->autoselect) {
	    if (in_play(unit)
		&& unit->id == map->curunit_id
		&& side_controls_unit(dside, unit)
		&& unit->act
		&& unit->act->acp > 0
		&& (unit->plan ? !unit->plan->asleep : TRUE)
		&& (unit->plan ? !unit->plan->reserve : TRUE)
		&& (unit->plan ? !unit->plan->delayed : TRUE)
		) {
		if (!in_middle(map, unit->x, unit->y)
		    && !map->scrolled_to_unit) {
		    put_on_screen(map, unit->x, unit->y);
		    map->scrolled_to_unit = TRUE;
		}
	    } else {
		unit2 = autonext_unit_inbox(dside, unit, widget_vp(map));
		if (unit2
		    && unit2->plan
		    && !unit2->plan->asleep
		    && !unit2->plan->reserve
		    && !unit2->plan->delayed
		    && unit2->plan->waitingfortasks) {
		    /* Use this unit */
		} else {
		    if (!in_play(unit)
			|| unit->id != map->curunit_id
			|| !side_controls_unit(dside, unit))
		      unit = NULL;
		    unit2 = autonext_unit(dside, unit);
		}
		if (unit2 != unit)
		  map->scrolled_to_unit = FALSE;
		if (unit2 != NULL)
		  set_current_unit(map, unit2);
	    }
	} else {
	    /* Even when not auto-selecting, the selected unit may pass
	       out of our control. */
	    if (!in_play(unit)
		|| unit->id != map->curunit_id
		|| !side_controls_unit(dside, unit))
	      unit = NULL;
	    set_current_unit(map, unit);
	}
	update_mouseover(map, lastrawx, lastrawy);
    }
    record_activity_end(activity, 0);
    return TCL_OK;
}

int
tk_animate_selection(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    Map *map;

    if (active_display(dside)) {
	for_all_maps(map) {
	    if (map->autoselect
		&& map->widget != NULL
		&& in_play(map->curunit)
		&& map->curunit->id == map->curunit_id) {
		map->anim_state = (map->anim_state + 1) % 8;
		update_at_unit(map, map->curunit);
	    }
	}
    }
    return TCL_OK;
}

int
tk_set_unit_type(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int u;

    u = strtol(argv[1], NULL, 10);
    dside->ui->curmap->inptype = u;
    return TCL_OK;
}

int
tk_mouse_down_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int rawx, rawy, button;

    rawx = strtol(argv[1], NULL, 10);  rawy = strtol(argv[2], NULL, 10);
    button = strtol(argv[3], NULL, 10);
    DGprintf("down %d %d %d\n", rawx, rawy, button);
    handle_mouse_down(dside->ui->curmap, rawx, rawy, button);
    return TCL_OK;
}

int
tk_mouse_up_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int rawx, rawy, button;

    rawx = strtol(argv[1], NULL, 10);  rawy = strtol(argv[2], NULL, 10);
    button = strtol(argv[3], NULL, 10);
    DGprintf("down %d %d %d\n", rawx, rawy, button);
    handle_mouse_up(dside->ui->curmap, rawx, rawy, button);
    return TCL_OK;
}

int
tk_mouse_over_cmd(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int rawx, rawy;

    rawx = strtol(argv[1], NULL, 10);  rawy = strtol(argv[2], NULL, 10);
    if (dside->designer)
      paint_on_drag(dside->ui->curmap, rawx, rawy, 0);
    update_mouseover(dside->ui->curmap, rawx, rawy);
    return TCL_OK;
}

void
update_mouseover(Map *map, int rawx, int rawy)
{
    DGprintf("over %d %d\n", rawx, rawy);
    tmpbuf[0] = '\0';
    if ((rawx >= 0 || rawy >= 0) && map->widget != NULL) {
	oneliner(dside, widget_vp(map), rawx, rawy);
	eval_tcl_cmd("update_mouseover \"%s\"", tmpbuf);
    }
    lastrawx = rawx;  lastrawy = rawy;
}

int
tk_world_mouse_down_cmd(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[])
{
    int rawx, rawy, button;

    rawx = strtol(argv[1], NULL, 10);  rawy = strtol(argv[2], NULL, 10);
    button = strtol(argv[3], NULL, 10);
    DGprintf("world down %d %d %d\n", rawx, rawy, button);
    handle_world_mouse_down(dside->ui->curmap, rawx, rawy, button);
    return TCL_OK;
}

int
tk_world_mouse_up_cmd(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[])
{
    int rawx, rawy, button;

    rawx = strtol(argv[1], NULL, 10);  rawy = strtol(argv[2], NULL, 10);
    button = strtol(argv[3], NULL, 10);
    DGprintf("world up %d %d %d\n", rawx, rawy, button);
    handle_world_mouse_up(dside->ui->curmap, rawx, rawy, button);
    return TCL_OK;
}

void
set_current_unit(Map *map, Unit *unit)
{
    Unit *oldunit = map->curunit;

    if (unit == oldunit)
      return;
    if (unit == NULL || (in_play(unit) && side_controls_unit(dside, unit))) {
	map->curunit = unit;
	map->curunit_id = (unit ? unit->id : 0);
    }
    /* Make sure the unit is actually visible on-screen. */
    if (unit != NULL) {
	put_on_screen(map, unit->x, unit->y);
	map->scrolled_to_unit = TRUE;
    }
    /* (should only do this if map not scrolled) */
    if (oldunit)
      update_at_unit(map, oldunit);
    if (map->curunit)
      update_at_unit(map, map->curunit);
    draw_unit_info(map);
    update_action_controls_info(map);
}

int empty_unit_info;

void
draw_unit_info(Map *map)
{
    char infobuf[BUFSIZE];
    int u, i, mrow, x = -1, y = -1;
    Unit *unit;
    Task *task;
    ImageFamily *uimf, *eimf;

    unit = map->curunit;
    if (!in_play(unit)) {
	if (!empty_unit_info) {
	    eval_tcl_cmd("update_unit_info curunit 0");
	    eval_tcl_cmd("update_unit_info handle \"(no unit)\"");
	    eval_tcl_cmd("update_unit_info loc \"\"");
	    eval_tcl_cmd("update_unit_info occ \"\"");
	    eval_tcl_cmd("update_unit_info hp \"\"");
	    eval_tcl_cmd("update_unit_info stack \"\"");
	    eval_tcl_cmd("update_unit_info s0 \"\"");
	    eval_tcl_cmd("update_unit_info s1 \"\"");
	    eval_tcl_cmd("update_unit_info s2 \"\"");
	    eval_tcl_cmd("update_unit_info s3 \"\"");
	    eval_tcl_cmd("update_unit_info plan \"\"");
	    eval_tcl_cmd("update_unit_info t0 \"\"");
	    eval_tcl_cmd("update_unit_info t1 \"\"");
	    eval_tcl_cmd("update_unit_picture \"(no)\" \"(no)\"");
	    empty_unit_info = TRUE;
	}
	return;
    }
    empty_unit_info = FALSE;
    u = unit->type;
    eval_tcl_cmd("update_unit_info curunit %d", unit->id);
    /* Update the image displayed. */
    uimf = dside->ui->uimages[unit->type];
    eimf = dside->ui->eimages[side_number(unit->side)];
    eval_tcl_cmd("update_unit_picture \"%s\" \"%s\"",
		 (uimf ? uimf->name : "(no)"),
		 (eimf ? eimf->name : "(no)"));
    /* Say which unit this is. */
    eval_tcl_cmd("update_unit_info handle \"%s\"", unit_handle(dside, unit));
    x = unit->x;  y = unit->y;
    location_desc(infobuf, dside, unit, u, x, y);
    eval_tcl_cmd("update_unit_info loc \"%s\"", infobuf);
    /* Very briefly list the numbers and types of the occupants. */
    occupants_desc(infobuf, unit);
    eval_tcl_cmd("update_unit_info occ \"%s\"", infobuf);
    /* Display the "important" parameters. */
    /* (should say something about parts?) */
    hp_desc(infobuf, unit, TRUE);
    strcat(infobuf, "   ");
    acp_desc(tmpbuf, unit, TRUE);
    strcat(infobuf, tmpbuf);
    cxp_desc(tmpbuf, unit, TRUE);
    strcat(infobuf, tmpbuf);
    morale_desc(tmpbuf, unit, TRUE);
    strcat(infobuf, tmpbuf);
    eval_tcl_cmd("update_unit_info hp \"%s\"", infobuf);
#if 0
    if (unit->hp * 4 <= u_hp(unit->type))
      eval_tcl_cmd(".m1.leftside.uf.unitinfo itemconfig hp -fill red");
    else if (unit->hp * 2 <= u_hp(unit->type))
      eval_tcl_cmd(".m1.leftside.uf.unitinfo itemconfig hp -fill orange");
    else
      eval_tcl_cmd(".m1.leftside.uf.unitinfo itemconfig hp -fill black");
#endif
    /* List other stack members here. */
    others_here_desc(infobuf, unit);
    eval_tcl_cmd("update_unit_info stack \"%s\"", infobuf);
    /* Describe the state of all the supplies. */
    for (mrow = 0; mrow < 4; ++mrow) {
	supply_desc(infobuf, unit, mrow);
	eval_tcl_cmd("update_unit_info s%d \"%s\"", mrow, infobuf);
    }
    /* Describe the current plan and task agenda. */
    plan_desc(infobuf, unit);
    eval_tcl_cmd("update_unit_info plan \"%s\"", infobuf);
    task = (unit->plan ? unit->plan->tasks : NULL);
    for (i = 0; i < 2; ++i) {
	task_desc(infobuf, unit->side, unit, task);
	eval_tcl_cmd("update_unit_info t%d \"%s\"", i, infobuf);
	if (task)
	  task = task->next;
    }
}

void
ui_mainloop(void)
{
    Tk_MainLoop();
}

/* All update_xxx_display callbacks are here. */

void
update_area_display(Side *side)
{
    Map *map;

    if (!active_display(side))
      return;
    for_all_maps(map)
      redraw_map(map);
}

/* Draw an individual detailed hex, as a row of one, on all maps. */

void
update_cell_display(Side *side, int x, int y, int flags)
{
    int dir, x1, y1;
    Map *map;
    VP *vp;

    if (!active_display(side))
      return;
    for_all_maps(map) {
	update_at_cell(map, x, y, flags);
	if (flags & UPDATE_ADJ) {
	    vp = widget_vp(map);
	    if ((side->terrview != NULL && vp->hw > 10)
		|| vp->draw_people
		|| vp->draw_control
		|| vp->draw_feature_boundaries
		|| (vp->draw_elevations && numdesigners > 0)
		) {
		for_all_directions(dir) {
		    if (point_in_dir(x, y, dir, &x1, &y1)
			/* A totally unseen adjacent cell will have
			   nothing worth redrawing. */
			&& terrain_view(dside, x1, y1) != UNSEEN) {
			update_at_cell(map, x1, y1, flags);
		    }
		}
	    }
	}
    }
}

/* The kernel calls this to update info about the given side. */

void
update_side_display(Side *side, Side *side2, int rightnow)
{
    int m;
    char sidebuf[BUFSIZE];

    if (!active_display(side))
      return;
    if (side2 == NULL)
      return;

    /* Build up and write the textual description of the side. */
    sidebuf[0] = '\0';
#ifdef DESIGNERS
    if (side2->designer)
      strcat(sidebuf, "(designer)");
#endif /* DESIGNERS */
    strcat(sidebuf, short_side_title(side2));
    if (side2->willingtodraw) {
	strcat(sidebuf, "[draw]");
    }
    if (side2->player) {
	strcat(sidebuf, "(");
	short_player_title(sidebuf+strlen(sidebuf), side2->player, NULL);
	strcat(sidebuf, ")");
    }
    eval_tcl_cmd("update_game_side_info %d {%s} %d %d %d",
		 side_number(side2), sidebuf, side2->everingame, side2->ingame,
		 side2->status);
    update_side_progress_display(side, side2);
    update_side_score_display(side, side2);
    for_all_material_types(m) {
	if (m_treasury(m)) {
	    eval_tcl_cmd("update_side_treasury %d %d %d",
			 side_number(side2), mtype_indexes[m],
			 side2->treasury[m]);
	}
    }
}

static void
update_side_progress_display(Side *side, Side *side2)
{
    int totacp, percentleft, percentresv, activ;
    extern int curpriority;

    totacp = percentleft = percentresv = 0;
    if (side2->ingame && !endofgame) {
	totacp = side_initacp(side2);
	if (totacp > 0) {
	    percentleft = (100 * side_acp(side2)) / totacp;
	    percentleft = limitn(0, percentleft, 100);
	    percentresv = (100 * side_acp_reserved(side2)) / totacp;
	    percentresv = limitn(0, percentresv, percentleft);
	}
    }
    activ = (!g_use_side_priority() || curpriority == side2->priority);
    eval_tcl_cmd("update_side_progress %d %d %d %d",
		 side_number(side2), activ, percentleft, percentresv);
}

static void
update_side_score_display(Side *side, Side *side2)
{
    int i;
    char *scoredesc;
    Scorekeeper *sk;

    if (keeping_score() && side2->everingame) {
	i = 0;
	for_all_scorekeepers(sk) {
	    scoredesc = side_score_desc(spbuf, side2, sk);
	    eval_tcl_cmd("update_game_side_score score%d_%d \"%s\"",
			 i, side_number(side2), scoredesc);
	    ++i;
	}
    }
}

/* The kernel calls this to update info about the given unit. */

void
update_unit_display(Side *side, Unit *unit, int rightnow)
{
    Map *map;

    if (!active_display(side))
      return;
    if (unit == NULL)
      return;
    if (inside_area(unit->x, unit->y)) {
	update_cell_display(side, unit->x, unit->y, UPDATE_ALWAYS);
    }
    for_all_maps(map) {
	if (unit == map->curunit) {
	    draw_unit_info(map);
	    update_action_controls_info(map);
	}
    }
    if (unit->side != NULL) {
	update_side_progress_display(side, unit->side);
	update_side_score_display(side, unit->side);
    }
    update_unit_type_list(unit->type);
}

void
update_unit_acp_display(Side *side, Unit *unit, int rightnow)
{
    if (!active_display(side))
      return;

    update_unit_type_list(unit->type);
}

void
update_action_result_display(Side *side, Unit *unit, int rslt, int rightnow)
{
    Map *map;

    if (!active_display(side))
      return;

    update_unit_type_list(unit->type);

    for_all_maps(map) {
	if (unit == map->curunit)
	  map->scrolled_to_unit = FALSE;
    }
}

/* The kernel calls this to update the global game state. */

int told_outcome;

void
update_turn_display(Side *side, int rightnow)
{
    Map *map;
    Unit *unit;

    if (!active_display(side))
      return;

    eval_tcl_cmd("update_game_state \"%s\"", curdatestr);
    for_all_maps(map) {
	if ((unit = map->curunit) != NULL) {
	    if (inside_area(unit->x, unit->y)) {
		update_cell_display(side, unit->x, unit->y, UPDATE_ALWAYS);
	    }
	    draw_unit_info(map);
	}
    }
    if (endofgame && !told_outcome && side == dside) {
	dside->may_set_see_all = TRUE;
	update_view_controls_info();
	if (!dside->see_all) {
	    dside->see_all = TRUE;
	    /* Force every map into a see-all/survey mode. */
	    for_all_maps(map) {
		map->see_all = TRUE;
		map->mode = survey_mode;
		map->autoselect = TRUE;
		map->move_on_click = FALSE;
		redraw_map(map);
		eval_tcl_cmd("update_mode survey");
	    }
	    if (side_won(dside)) {
		eval_tcl_cmd("popup_game_over_dialog won");
	    } else if (side_lost(dside)) {
		eval_tcl_cmd("popup_game_over_dialog lost");
	    } else {
		eval_tcl_cmd("popup_game_over_dialog over");
	    }
	}
	told_outcome = TRUE;
    }
}

void
update_action_display(Side *side, int rightnow)
{
    Map *map;
    Unit *unit;

    if (!active_display(side))
      return;

    for_all_maps(map) {
	if ((unit = map->curunit) != NULL) {
	    if (inside_area(unit->x, unit->y)) {
		update_cell_display(side, unit->x, unit->y, UPDATE_ALWAYS);
	    }
	    draw_unit_info(map);
	}
	update_action_controls_info(map);
    }
}

void
update_event_display(Side *side, HistEvent *hevt, int rightnow)
{
}

void
update_fire_at_display(Side *side, Unit *unit, Unit *unit2, int m, int now)
{
    Map *map;

    if (!active_display(side))
      return;
    for_all_maps(map) {
	draw_fire_line(map, unit, unit2, 0, 0);
    }
}

/* This is for animation of fire-into actions. */

void
update_fire_into_display(Side *side, Unit *unit, int x, int y, int z, int m,
			 int rightnow)
{
    Map *map;

    if (!active_display(side))
      return;
    for_all_maps(map) {
	draw_fire_line(map, unit, NULL, x, y);
    }
}

/* Updates to clock need to be sure that display changes immediately. */

void
update_clock_display(Side *side, int rightnow)
{
}

void
update_message_display(Side *side, Side *sender, char *str, int rightnow)
{
    if (!active_display(side))
      return;

    notify(side, "From %s: \"%s\"",
	   (sender != NULL ? short_side_title(sender) : "<anon>"), str);
}

void
update_all_progress_displays(char *str, int s)
{
}

void
action_point(Side *side, int x, int y)
{
    Map *map;

    if (!active_display(side))
      return;
    if (!inside_area(x, y))
      return;

    for_all_maps(map) {
	if (map->follow_action && !in_middle(map, x, y)) {
	    put_on_screen(map, x, y);
	    map->scrolled_to_unit = TRUE;
	}
    }
}

void
flush_display_buffers(Side *side)
{
}

void
update_everything()
{
}

/* This is called by generic Unix crash-handling code, no need for
   us to do anything special. */

void
close_displays()
{
}

int
schedule_movie(Side *side, char *movie, ...)
{
    va_list ap;
    int i;
    enum movie_type itype;

    if (!active_display(side))
      return FALSE;
    if (side->ui->numscheduled >= 10)
      return FALSE;
    memset(&(side->ui->movies[side->ui->numscheduled]), 0, sizeof(struct a_movie));
    side->ui->movies[side->ui->numscheduled].type = movie;
    itype = movie_null;
    if (strcmp(movie, "miss") == 0)
      itype = movie_miss;
    else if (strcmp(movie, "hit") == 0)
      itype = movie_hit;
    else if (strcmp(movie, "death") == 0)
      itype = movie_death;
    else if (strcmp(movie, "nuke") == 0)
      itype = movie_nuke;
    else if (strcmp(movie, "sound") == 0)
      itype = movie_sound;
    else if (strcmp(movie, "flash") == 0)
      itype = movie_flash;
    side->ui->movies[side->ui->numscheduled].itype = itype;
    va_start(ap, movie);
    for (i = 0; i < 5; ++i)
      side->ui->movies[side->ui->numscheduled].args[i] = va_arg(ap, int);
    va_end(ap);
    ++side->ui->numscheduled;
    return TRUE;
}

void
play_movies(SideMask sidemask)
{
    int j, unitid, btype;
    Map *map;
    Unit *unit;

    if (!active_display(dside))
      return;
    if (side_in_set(dside, sidemask)) {
	for (j = 0; j < dside->ui->numscheduled; ++j) {
	    btype = -1;
	    switch (dside->ui->movies[j].itype) {
	      case movie_null:
		break;
	      case movie_miss:
		if (btype < 0)
		  btype = 0;
	      case movie_hit:
		if (btype < 0)
		  btype = 1;
	      case movie_death:
		if (btype < 0)
		  btype = 2;
		unitid = dside->ui->movies[j].args[0];
		unit = find_unit(unitid);
		if (unit == NULL || !in_area(unit->x, unit->y))
		  continue;
		for_all_maps(map) {
		    draw_blast(map, unit, btype);
		}
		break;
	      case movie_nuke:
		break;
	      case movie_flash:
		unitid = dside->ui->movies[j].args[0];
		unit = find_unit(unitid);
		if (unit == NULL || !in_area(unit->x, unit->y))
		  continue;
		for_all_maps(map) {
		    draw_blast(map, unit, 3);
		}
		break;
	      default:
		break;
	    }
	}
	dside->ui->numscheduled = 0;
    }
}

/* Beep the beeper! */

void
beep(Side *side)
{
    eval_tcl_cmd("bell");
}

void
low_notify(Side *side, char *str)
{
    eval_tcl_cmd("low_notify {%s\n}", str);
}

void
popup_game_dialog(void)
{
    eval_tcl_cmd("popup_splash_screen");
}

int last_num_units_in_play[MAXUTYPES];
int last_num_units_incomplete[MAXUTYPES];

void
init_redraws(void)
{
    int u;
    Side *side2;
    Map *map;

    if (dside->ui != NULL) {
	/* The moment of truth - up to now output has been suppressed. */
	dside->ui->active = TRUE;
    }

    for_all_sides(side2) {
	update_side_display(dside, side2, TRUE);
    }
    for_all_unit_types(u) {
	last_num_units_in_play[u] = last_num_units_incomplete[u] = -1;
	init_unit_type_list(u);
	update_unit_type_list(u);
    }
    update_turn_display(dside, TRUE);

    for_all_maps(map)
      set_tool_cursor(dside, map);
}

static void
init_unit_type_list(int u)
{
    int i;
    extern int longest_shortest;

    if (!between(0, u, numutypes))
      return;
    i = utype_indexes[u];
    /* Only show a char for the unit type if all have single chars,
       otherwise blank all. */
    eval_tcl_cmd("update_unitlist_char %d \"%c\"",
		 i, (longest_shortest == 1 ? *(shortest_unique_name(u)) : ' '));
    /* (should do other columns too) */
    eval_tcl_cmd("update_unitlist_name %d \"%s\"", i, u_type_name(u));
}

static void
update_unit_type_list(int u)
{
    int i, num;

    if (!between(0, u, numutypes))
      return;
    i = utype_indexes[u];
    /* Our unit total (right-justified) */
    num = num_units_in_play(dside, u);
    if (num != last_num_units_in_play[u]) {
	spbuf[0] = '\0';
	if (num > 0) {
	    sprintf(spbuf, "%d", num);
	}
	eval_tcl_cmd("update_unitlist_count %d \"%s\"", i, spbuf);
	last_num_units_in_play[u] = num;
    }
    /* Our units under construction (left-justified) */
    num = num_units_incomplete(dside, u);
    if (num != last_num_units_incomplete[u]) {
	spbuf[0] = '\0';
	if (num > 0) {
	    sprintf(spbuf, "(%d)", num);
	}
	eval_tcl_cmd("update_unitlist_incomplete %d \"%s\"", i, spbuf);
	last_num_units_incomplete[u] = num;
    }
    /* (should do other columns too) */
}

/* Transfer tcl preference settings to C code preferences. */

int
tk_save_prefs(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char *strval, prefbuf[5000];
    FILE *fp;

    strval = Tcl_GetVar2(interp, "prefs", "power", TCL_GLOBAL_ONLY);
    default_vp.power = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "grid", TCL_GLOBAL_ONLY);
    default_vp.draw_grid = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "coverage", TCL_GLOBAL_ONLY);
    default_vp.draw_cover = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "elevations", TCL_GLOBAL_ONLY);
    default_vp.draw_elevations = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "people", TCL_GLOBAL_ONLY);
    default_vp.draw_people = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "control", TCL_GLOBAL_ONLY);
    default_vp.draw_control = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "temperature", TCL_GLOBAL_ONLY);
    default_vp.draw_temperature = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "winds", TCL_GLOBAL_ONLY);
    default_vp.draw_winds = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "clouds", TCL_GLOBAL_ONLY);
    default_vp.draw_clouds = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "unit_names", TCL_GLOBAL_ONLY);
    default_vp.draw_names = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "feature_names", TCL_GLOBAL_ONLY);
    default_vp.draw_feature_names = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "feature_boundaries", TCL_GLOBAL_ONLY);
    default_vp.draw_feature_boundaries = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "meridians", TCL_GLOBAL_ONLY);
    default_vp.draw_latlong = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "meridian_interval", TCL_GLOBAL_ONLY);
    default_vp.latlong_interval = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "ai", TCL_GLOBAL_ONLY);
    default_vp.draw_ai = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "terrain_images", TCL_GLOBAL_ONLY);
    default_draw_terrain_images = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "terrain_patterns", TCL_GLOBAL_ONLY);
    default_draw_terrain_patterns = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "transitions", TCL_GLOBAL_ONLY);
    default_draw_transitions = strtol(strval, NULL, 10);
    strval = Tcl_GetVar2(interp, "prefs", "font_family", TCL_GLOBAL_ONLY);
    default_font_family = copy_string(strval);
    strval = Tcl_GetVar2(interp, "prefs", "font_size", TCL_GLOBAL_ONLY);
    default_font_size = strtol(strval, NULL, 10);
    ui_update_state();
    sprintlisp(prefbuf, find_at_key(dside->uidata, "unix"), 5000);
    if ((fp = fopen(preferences_filename(), "w")) != NULL) {
	fputs(prefbuf, fp);
	fputs("\n", fp);
	fclose(fp);
    }
    return TCL_OK;
}

static void
ui_update_state(void)
{
    Obj *state = lispnil;

    push_binding(&state, intern_symbol("default-power"),
		 new_number(default_vp.power));
    push_binding(&state, intern_symbol("default-draw-grid"),
		 new_number(default_vp.draw_grid));
    push_binding(&state, intern_symbol("default-draw-coverage"),
		 new_number(default_vp.draw_cover));
    push_binding(&state, intern_symbol("default-draw-elevations"),
		 new_number(default_vp.draw_elevations));
    push_binding(&state, intern_symbol("default-draw-lighting"),
		 new_number(default_vp.draw_lighting));
    push_binding(&state, intern_symbol("default-draw-people"),
		 new_number(default_vp.draw_people));
    push_binding(&state, intern_symbol("default-draw-control"),
		 new_number(default_vp.draw_control));
    push_binding(&state, intern_symbol("default-draw-temperature"),
		 new_number(default_vp.draw_temperature));
    push_binding(&state, intern_symbol("default-draw-winds"),
		 new_number(default_vp.draw_winds));
    push_binding(&state, intern_symbol("default-draw-clouds"),
		 new_number(default_vp.draw_clouds));
    push_binding(&state, intern_symbol("default-draw-unit-names"),
		 new_number(default_vp.draw_names));
    push_binding(&state, intern_symbol("default-draw-feature-names"),
		 new_number(default_vp.draw_feature_names));
    push_binding(&state, intern_symbol("default-draw-feature-boundaries"),
		 new_number(default_vp.draw_feature_boundaries));
    push_binding(&state, intern_symbol("default-draw-meridians"),
		 new_number(default_vp.draw_latlong));
    push_binding(&state, intern_symbol("default-meridian-interval"),
		 new_number(default_vp.latlong_interval));
    push_binding(&state, intern_symbol("default-draw-ai"),
		 new_number(default_vp.draw_ai));
    push_binding(&state, intern_symbol("default-draw-terrain-images"),
		 new_number(default_draw_terrain_images));
    push_binding(&state, intern_symbol("default-draw-terrain-patterns"),
		 new_number(default_draw_terrain_patterns));
    push_binding(&state, intern_symbol("default-draw-transitions"),
		 new_number(default_draw_transitions));
    push_binding(&state, intern_symbol("default-font-family"),
		 new_string(default_font_family));
    push_binding(&state, intern_symbol("default-font-size"),
		 new_number(default_font_size));

    dside->uidata = replace_at_key(dside->uidata, "unix", state);
}

static void interp_unix_ui_data(Obj *uispec);

void
get_preferences(void)
{
    int startlineno = 0, endlineno = 0;
    Obj *uispec;
    FILE *fp;

    if ((fp = fopen(preferences_filename(), "r")) != NULL) {
	uispec = read_form(fp, &startlineno, &endlineno);
	interp_unix_ui_data(uispec);
	fclose(fp);
    }
}

/* Given a list of preference specifications, decipher them and set
   appropriate C variables, also pass into tcl preferences code. */

static void
interp_unix_ui_data(Obj *uispec)
{
    int numval;
    char *strval = NULL;
    char *name;
    Obj *rest, *bdg;

    for_all_list(uispec, rest) {
	bdg = car(rest);
	if (!consp(bdg)) {
	    /* Don't complain out loud normally, confusing to users
	       because preferences are under Xconq and not user
	       control. */
	    Dprintf("Syntax error in preference binding?\n");
	    continue;
	}
	if (symbolp(car(bdg))) {
	    name = c_string(car(bdg));
	    strval = NULL;
	    numval = 0;
	    if (numberp(cadr(bdg))) {
		numval = c_number(cadr(bdg));
	    } else if (stringp(cadr(bdg))) {
		strval = c_string(cadr(bdg));
	    } else {
		Dprintf("Preference property `%s' not a number or string, setting to zero\n",
			name);
	    }
	    if (strcmp(name, "default-power") == 0) {
		default_vp.power = numval;
		eval_tcl_cmd("set_pref_value power %d", numval);
	    } else if (strcmp(name, "default-draw-grid") == 0) {
		default_vp.draw_grid = numval;
		eval_tcl_cmd("set_pref_value grid %d", numval);
	    } else if (strcmp(name, "default-draw-coverage") == 0) {
		default_vp.draw_cover = numval;
		eval_tcl_cmd("set_pref_value coverage %d", numval);
	    } else if (strcmp(name, "default-draw-elevations") == 0) {
		default_vp.draw_cover = numval;
		eval_tcl_cmd("set_pref_value elevations %d", numval);
	    } else if (strcmp(name, "default-draw-lighting") == 0) {
		default_vp.draw_lighting = numval;
		eval_tcl_cmd("set_pref_value lighting %d", numval);
	    } else if (strcmp(name, "default-draw-people") == 0) {
		default_vp.draw_people = numval;
		eval_tcl_cmd("set_pref_value people %d", numval);
	    } else if (strcmp(name, "default-draw-control") == 0) {
		default_vp.draw_control = numval;
		eval_tcl_cmd("set_pref_value control %d", numval);
	    } else if (strcmp(name, "default-draw-temperature") == 0) {
		default_vp.draw_temperature = numval;
		eval_tcl_cmd("set_pref_value temperature %d", numval);
	    } else if (strcmp(name, "default-draw-winds") == 0) {
		default_vp.draw_winds = numval;
		eval_tcl_cmd("set_pref_value winds %d", numval);
	    } else if (strcmp(name, "default-draw-clouds") == 0) {
		default_vp.draw_clouds = numval;
		eval_tcl_cmd("set_pref_value clouds %d", numval);
	    } else if (strcmp(name, "default-draw-unit-names") == 0) {
		default_vp.draw_names = numval;
		eval_tcl_cmd("set_pref_value unit_names %d", numval);
	    } else if (strcmp(name, "default-draw-feature-names") == 0) {
		default_vp.draw_feature_names = numval;
		eval_tcl_cmd("set_pref_value feature_names %d", numval);
	    } else if (strcmp(name, "default-draw-feature-boundaries") == 0) {
		default_vp.draw_feature_boundaries = numval;
		eval_tcl_cmd("set_pref_value feature_boundaries %d", numval);
	    } else if (strcmp(name, "default-draw-meridians") == 0) {
		default_vp.draw_latlong = numval;
		eval_tcl_cmd("set_pref_value meridians %d", numval);
	    } else if (strcmp(name, "default-meridian-interval") == 0) {
		default_vp.latlong_interval = numval;
		eval_tcl_cmd("set_pref_value meridian_interval %d", numval);
	    } else if (strcmp(name, "default-draw-ai") == 0) {
		default_vp.draw_ai = numval;
		eval_tcl_cmd("set_pref_value ai %d", numval);
	    } else if (strcmp(name, "default-draw-terrain-images") == 0) {
		default_draw_terrain_images = numval;
		eval_tcl_cmd("set_pref_value terrain_images %d", numval);
	    } else if (strcmp(name, "default-draw-terrain-patterns") == 0) {
		default_draw_terrain_patterns = numval;
		eval_tcl_cmd("set_pref_value terrain_patterns %d", numval);
	    } else if (strcmp(name, "default-draw-transitions") == 0) {
		default_draw_transitions = numval;
		eval_tcl_cmd("set_pref_value transitions %d", numval);
	    } else if (strcmp(name, "default-font-family") == 0) {
		default_font_family = copy_string(strval);
		eval_tcl_cmd("set_pref_value font_family \"%s\"", strval);
	    } else if (strcmp(name, "default-font-size") == 0) {
		default_font_size = numval;
		eval_tcl_cmd("set_pref_value font_size %d", numval);
	    } else {
		/* Note unrecognized properties, but don't bother the user. */
		Dprintf("Preference binding `%s' unrecognized\n", name);
	    }
	} else {
	    /* As with above comment. */
	    Dprintf("Syntax error in preference binding head?\n");
	}
    }
}

void
popup_help(Side *side, HelpNode *node)
{
    eval_tcl_cmd("create_help_window");
}

HelpNode *cur_help_node;

static void describe_map(int arg, char *key, TextBuffer *buf);

static void
describe_map(int arg, char *key, TextBuffer *buf)
{
    tbcat(buf, "In move mode:\n");
    tbcat(buf, "  The next unit that can do anything will be selected automatically.\n");
    tbcat(buf, "  Left-click on a destination to move the selected unit there.\n");
#if 0
    tbcat(buf, "  To select a unit, center-click it.\n");
#endif
    tbcat(buf, "\n");
    tbcat(buf, "In survey mode:\n");
    tbcat(buf, "  Left-click on a unit to make the current one.\n");
    tbcat(buf, "  To move it, right-click, use 'm'ove command or 'z' to switch back ");
    tbcat(buf, "to move mode.\n");
    tbcat(buf, "\n");
#if 0
#ifdef DESIGNERS
    tbcat(buf, "In design mode:\n");
    tbcat(buf, "  Left-click to perform the tool-specific action: add unit or paint ");
    tbcat(buf, "terrain, population, or feature.\n");
    tbcat(buf, "  Shift-left-click to perform the tool-specific current object ");
    tbcat(buf, "selection: pick current unit type, terrain type, side, or feature.\n");
    tbcat(buf, "\n");
#endif /* DESIGNERS */
    tbcat(buf, "In any mode:\n");
    tbcat(buf, "  Center-click on a unit or cell to make the current one.\n");
    tbcat(buf, "  Drag-shift-center-click to measure distance (gasp! hold down Shift, ");
    tbcat(buf, "press center button at origin, drag to destination, release button ");
    tbcat(buf, "and Shift).\n");
    tbcat(buf, "  Right-click on a destination to move the selected unit there.\n");
    tbcat(buf, "  Move the mouse while holding Meta to change the current cell.\n");
    tbcat(buf, "  Press a keypad key to scroll the look.\n");
#endif
}

int
tk_help_goto(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char *arg = argv[1];
    char *nclassname;
    char tclbuf[15000];
    int rslt;
    HelpNode *node, *tmp;

    /* If this is the first time we came here, build up the table of
       contents. */
    if (cur_help_node == NULL) {
	key_commands_help_node =
	  add_help_node("commands", describe_key_commands, 0, first_help_node);
	long_commands_help_node =
	  add_help_node("long commands", describe_long_commands, 0,
			key_commands_help_node);
	add_help_node("map", describe_map, 0, first_help_node);
	eval_tcl_cmd("add_help_topic_key \"%s\"", first_help_node->key);
	for (tmp = first_help_node->next; tmp != first_help_node; tmp = tmp->next) {
	    eval_tcl_cmd("add_help_topic_key \"%s\"", tmp->key);
	}
	cur_help_node = first_help_node;
    }
    if (strcmp(arg, "help") == 0) {
	/* (should go to help node) */
    } else if (strcmp(arg, "prev") == 0) {
	cur_help_node = cur_help_node->prev;
    } else if (strcmp(arg, "next") == 0) {
	cur_help_node = cur_help_node->next;
    } else if (strcmp(arg, "back") == 0) {
	/* (should go back) */
    } else {
	node = find_help_node(cur_help_node, arg);
	if (node)
	  cur_help_node = node;
	else
	  beep(dside);
    }
    get_help_text(cur_help_node);
    /* Make a string representing the node class. */
    switch (cur_help_node->nclass) {
      case utypenode:
	nclassname = "u";
	break;
      case mtypenode:
	nclassname = "m";
	break;
      case ttypenode:
	nclassname = "t";
	break;
      case atypenode:
	nclassname = "a";
	break;
      default:
	nclassname = "?";
    }
    sprintf (tclbuf, "update_help {%s} {%s} {%s} %d",
	     cur_help_node->key, cur_help_node->text, nclassname,
	     cur_help_node->arg);
    rslt = Tcl_Eval(interp, tclbuf);
    if (rslt == TCL_ERROR) {
	fprintf(stderr, "Error: %s\n", interp->result);
	fprintf(stderr, "Error: while updating help node %s\n",
		cur_help_node->key);
    }
    return TCL_OK;
}

/* Given a typed character, decide what to do with it. */

int
tk_interp_key(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int rawx, rawy, winx, winy, x, y, cancelled;
    Map *map = dside->ui->curmap;
    Tk_Window tkwin;
    void (*fn)(Side *sidex, Map *mapx, int cancelledx);

    map->inpch = *(argv[1]);
    rawx = strtol(argv[2], NULL, 10);  rawy = strtol(argv[3], NULL, 10);
    tkwin = Tk_CoordsToWindow(rawx, rawy, Tk_MainWindow(interp));
    map->inpsx = map->inpsy = -1;
    map->inpx = map->inpy = -1;
    if (tkwin && map->widget != NULL) {
	Tk_GetRootCoords(tkwin, &winx, &winy);
	map->inpsx = rawx - winx;  map->inpsy = rawy - winy;
	if (nearest_cell(widget_vp(map), map->inpsx, map->inpsy,
			 &x, &y, NULL, NULL)) {
	    map->inpx = x;  map->inpy = y;
	}
    }
    DGprintf("key %d %d -> %d %d\n", map->inpsx, map->inpsy, map->inpx, map->inpy);

    if (isdigit(map->inpch)) {
	if (map->prefixarg < 0) {
	    map->prefixarg = 0;
	} else {
	    map->prefixarg *= 10;
	}
	map->prefixarg += (map->inpch - '0');
	sprintf(interp->result, "%d", map->prefixarg);
	return TCL_OK;
    }
    /* Call the modal handler if defined, giving it side and cancel
       flag. */
    if (map->modalhandler) {
	fn = map->modalhandler;
	cancelled = (map->inpch == ESCAPE_CHAR);
	/* Remove the handler - will restore itself if needed. */
	map->modalhandler = NULL;
	(*fn)(dside, map, cancelled);
	if (cancelled) {
	    eval_tcl_cmd("clear_command_line");
	    notify(dside, "Cancelled.");
	}
    } else {
	dside->prefixarg = map->prefixarg;
	dside->ui->beepcount = 0;
	execute_command(dside, map->inpch);
    }
    /* Clear the command char, so menu selects and other issuers of
       commands aren't confused with keystroke commands. */
    map->inpch = '\0';
    /* Reset the prefix arg unless there is still more modal input
       to be read. */
    if (map->modalhandler == NULL)
      map->prefixarg = -1;
    sprintf(interp->result, "%d", map->prefixarg);
    return TCL_OK;
}

int
tk_execute_long_command(ClientData cldata, Tcl_Interp *interp,
			int argc, char *argv[])
{
    char *cmd = argv[1], *ncmd;

    /* If the long command has a numeric prefix, take it to be the
       prefix argument and peel off.  */
    if (isdigit(*cmd)) {
        dside->ui->curmap->prefixarg = strtol(cmd, &ncmd, 10);
	cmd = cmd;
    }
    execute_long_command(dside, cmd);
    return TCL_OK;
}

int
tk_set_design_tool(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[])
{
    char *arg = argv[1];
    Map *map;

    if (strcmp(arg, "normal") == 0) {
	dside->ui->curdesigntool = survey_mode;
    } else if (strcmp(arg, "terrain") == 0) {
	switch (t_subtype(dside->ui->curttype)) {
	  case bordersubtype:
	    dside->ui->curdesigntool = bord_paint_mode;
	    break;
	  case connectionsubtype:
	    dside->ui->curdesigntool = conn_paint_mode;
	    break;
	  case coatingsubtype:
	    dside->ui->curdesigntool = coat_paint_mode;
	    break;
	  default:
	    dside->ui->curdesigntool = cell_paint_mode;
	    break;
	}
    } else if (strcmp(arg, "unit") == 0) {
	dside->ui->curdesigntool = unit_paint_mode;
    } else if (strcmp(arg, "people") == 0) {
	dside->ui->curdesigntool = people_paint_mode;
    } else if (strcmp(arg, "control") == 0) {
	dside->ui->curdesigntool = control_paint_mode;
    } else if (strcmp(arg, "feature") == 0) {
	dside->ui->curdesigntool = feature_paint_mode;
    } else if (strcmp(arg, "material") == 0) {
	dside->ui->curdesigntool = material_paint_mode;
    } else if (strcmp(arg, "elevation") == 0) {
	dside->ui->curdesigntool = elevation_paint_mode;
    } else if (strcmp(arg, "temperature") == 0) {
	dside->ui->curdesigntool = temperature_paint_mode;
    } else if (strcmp(arg, "clouds") == 0) {
	dside->ui->curdesigntool = clouds_paint_mode;
    } else if (strcmp(arg, "winds") == 0) {
	dside->ui->curdesigntool = winds_paint_mode;
    } else if (strcmp(arg, "view") == 0) {
	dside->ui->curdesigntool = view_paint_mode;
    } else {
	run_warning("bogus design tool %s, ignoring\n", arg);
	return TCL_OK;
    }
    for_all_maps(map) {
	map->mode = dside->ui->curdesigntool;
	set_tool_cursor(dside, map);
    }
    return TCL_OK;
}

/* This macro implements cycling of a variable through a set of consecutive
   values, with direction controlled by the shift key.  If the limit is 0,
   then the cycling part is not done. */

#define OPTION_CYCLE(var, lo, hi, n, dir)  \
  if ((hi) - (lo) > 0) {  \
    (var) = (((var) + ((dir) < 0 ? -(n) : (n)) - (lo) + ((hi) - (lo))) % ((hi) - (lo))) + (lo);  \
  } else {  \
    (var) = ((var) + ((dir) < 0 ? -(n) : (n)));  \
  }

int
tk_set_design_data(ClientData cldata, Tcl_Interp *interp,
		   int argc, char *argv[])
{
    int set, dir, val;
    char *type = argv[1];

    if (strcmp(argv[2], "incr") == 0) {
	set = FALSE;
	dir = 1;
    } else if (strcmp(argv[2], "decr") == 0) {
	set = FALSE;
	dir = -1;
    } else {
	set = TRUE;
	val = strtol(argv[2], NULL, 10);
    }

    if (strcmp(type, "curttype") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curttype, 0, numttypes, 1, dir);
	    val = dside->ui->curttype;
	}
	dside->ui->curttype = val;
	switch (t_subtype(dside->ui->curttype)) {
	  case bordersubtype:
	    dside->ui->curdesigntool = bord_paint_mode;
	    break;
	  case connectionsubtype:
	    dside->ui->curdesigntool = conn_paint_mode;
	    break;
	  case coatingsubtype:
	    dside->ui->curdesigntool = coat_paint_mode;
	    break;
	  default:
	    dside->ui->curdesigntool = cell_paint_mode;
	    break;
	}
    } else if (strcmp(type, "curbgttype") == 0) {
	dside->ui->curbgttype = val;
    } else if (strcmp(type, "curutype") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curutype, 0, numutypes, 1, dir);
	    val = dside->ui->curutype;
	}
	dside->ui->curutype = val;
    } else if (strcmp(type, "curusidenumber") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curusidenumber, 0, numsides + 1, 1, dir);
	    val = dside->ui->curusidenumber;
	}
	dside->ui->curusidenumber = val;
    } else if (strcmp(type, "curpeoplenumber") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curpeoplenumber, 0, numsides + 1, 1, dir);
	    val = dside->ui->curpeoplenumber;
	}
	dside->ui->curpeoplenumber = val;
    } else if (strcmp(type, "curcontrolnumber") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curcontrolnumber, 0, numsides + 1, 1, dir);
	    val = dside->ui->curcontrolnumber;
	}
	dside->ui->curcontrolnumber = val;
    } else if (strcmp(type, "curfid") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curfid, 0, num_features(), 1, dir);
	    val = dside->ui->curfid;
	}
	dside->ui->curfid = val;
    } else if (strcmp(type, "curelev") == 0) {
	dside->ui->curelevation = val;
    } else if (strcmp(type, "curwinddir") == 0) {
	dside->ui->curwinddir = val;
    } else if (strcmp(type, "curwindforce") == 0) {
	dside->ui->curwindforce = val;
    } else if (strcmp(type, "curbrushradius") == 0) {
	if (!set) {
	    OPTION_CYCLE(dside->ui->curbrushradius, 0, 11, 1, dir);
	    val = dside->ui->curbrushradius;
	}
	dside->ui->curbrushradius = val;
    }
    set_tool_cursor(dside, dside->ui->curmap);
    sprintf(interp->result, "%d", val);
    return TCL_OK;
}

int
tk_create_new_feature(ClientData cldata, Tcl_Interp *interp,
		      int argc, char *argv[])
{
    extern int nextfid;
    Feature *feature;

    sprintf(spbuf, "%d", nextfid);
    feature = net_create_feature("feature", copy_string(spbuf));
    sprintf(interp->result, "%d", (feature ? feature->id : 0));
    return TCL_OK;
}

int
tk_designer_save(ClientData cldata, Tcl_Interp *interp,
		 int argc, char *argv[])
{
    Module *module;

    module = create_game_module(NULL);
    module->title = "Designer-saved data";
    module->compress_layers = TRUE;
    init_module_reshape(module);

    module->filename = copy_string(argv[1]);
    if (strstr(argv[2], " !compress "))
      module->compress_layers = FALSE;
    if (strstr(argv[2], " all "))
      module->def_all = TRUE;
    if (strstr(argv[2], " types "))
      module->def_types = TRUE;
    if (strstr(argv[2], " tables "))
      module->def_tables = TRUE;
    if (strstr(argv[2], " globals "))
      module->def_globals = TRUE;
    if (strstr(argv[2], " world "))
      module->def_world = TRUE;
    if (strstr(argv[2], " area "))
      module->def_areas = TRUE;
    if (strstr(argv[2], " terrain "))
      module->def_area_terrain = TRUE;
    if (strstr(argv[2], " areamisc "))
      module->def_area_misc = TRUE;
    if (strstr(argv[2], " weather "))
      module->def_area_weather = TRUE;
    if (strstr(argv[2], " material "))
      module->def_area_material = TRUE;
    if (strstr(argv[2], " sides "))
      module->def_sides = TRUE;
    if (strstr(argv[2], " views "))
      module->def_side_views = TRUE;
    if (strstr(argv[2], " units "))
      module->def_units = TRUE;
    if (!write_game_module(module))
      run_warning("Could not write the module \"%s\"!", module->filename);
    return TCL_OK;
}

int
tk_numutypes_available(ClientData cldata, Tcl_Interp *interp,
		       int argc, char *argv[])
{
    int num, u;

    num = 0;
    for_all_unit_types(u) {
	if (utype_indexes[u] >=0)
	  ++num;
    }
    sprintf(interp->result, "%d", num);
    return TCL_OK;
}

int
tk_utype_actual(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int n, u;

    n = strtol(argv[1], NULL, 10);
    for_all_unit_types(u) {
	if (utype_indexes[u] == n) {
	    sprintf(interp->result, "%d", u);
	    return TCL_OK;
	}
    }
    /* (should make error) */
    sprintf(interp->result, "%d", -1);
    return TCL_OK;
}

int
tk_mtype_actual(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int n, m;

    n = strtol(argv[1], NULL, 10);
    for_all_material_types(m) {
	if (mtype_indexes[m] == n) {
	    sprintf(interp->result, "%d", m);
	    return TCL_OK;
	}
    }
    /* (should make error) */
    sprintf(interp->result, "%d", -1);
    return TCL_OK;
}

int
tk_map_size_at_power(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    int pow;

    pow = strtol(argv[1], NULL, 10);
    sprintf(interp->result, "%d %d", area.width * hws[pow], 0);
    return TCL_OK;
}

int
tk_agreements(ClientData cldata, Tcl_Interp *interp, int argc, char *argv[])
{
    char listbuf[BUFSIZE];
    Agreement *ag;

    listbuf[0] = '\0';
    for_all_agreements(ag) {
	tprintf(listbuf, " %d", ag->id);
    }
    sprintf(interp->result, "%s", listbuf);
    return TCL_OK;
}

void
place_legends(side)
Side *side;
{
    int nf = side->ui->numfeatures;

    if (!features_defined() || nf <= 0)
      return;
    if (side->ui->legends == NULL)
      side->ui->legends = (Legend *) xmalloc((nf + 1) * sizeof(Legend));
    place_feature_legends(side->ui->legends, nf, side, 0, 1);
}

void
eval_tcl_cmd(char *fmt, ...)
{
    char tclbuf[500], backup[500];
    int rslt;
    va_list ap;

    if (empty_string(fmt))
      return;

    va_start(ap, fmt);
    vsprintf(tclbuf, fmt, ap);
    va_end(ap);
    strcpy(backup, tclbuf);
    rslt = Tcl_GlobalEval(interp, tclbuf);
    if (rslt == TCL_ERROR) {
	fprintf(stderr, "Error: %s\n", interp->result);
	fprintf(stderr, "Error: while evaluating `%s'\n", backup);
    }
}

/* True if any variants are available. */

int any_variants;

/* These are true when a standard variant is available to be chosen. */

int vary_world;
int vary_real_time;

/* These are the values that will be used to set world geometry. */

int new_circumference;
int new_width;
int new_height;
int new_latitude;
int new_longitude;
int new_scale;

int new_time_for_game;
int new_time_per_side;
int new_time_per_turn;

#define MAXCHECKBOXES 10

int numcheckboxes = MAXCHECKBOXES;

Variant *checkboxes[MAXCHECKBOXES];

int checkboxvalues[MAXCHECKBOXES];

Obj *variants;

/* Go through all the game's variants and set up appropriate flags. */

static void interpret_checkbox(Variant *var, int i);

static void
interpret_variants(void)
{
    int i;
    char *vartypename;
    Obj *vartmp;
    Variant *var;

    any_variants = FALSE;
    for (i = 0; i < MAXCHECKBOXES; ++i) {
	eval_tcl_cmd("set varianttext(%d) Unused", i);
	eval_tcl_cmd("set variantstate(%d) disabled", i);
    }
    vary_world = vary_real_time = FALSE;
    if (mainmodule == NULL || mainmodule->variants == NULL)
      return;
    numcheckboxes = 3;
    for (i = 0; mainmodule->variants[i].id != lispnil; ++i) {
	var = &(mainmodule->variants[i]);
	any_variants = TRUE;
	vartypename = c_string(var->id);
	switch (keyword_code(vartypename)) {
	  case K_WORLD_SEEN:
	    interpret_checkbox(var, 0);
	    break;
	  case K_SEE_ALL:
	    interpret_checkbox(var, 1);
	    break;
	  case K_SEQUENTIAL:
	    interpret_checkbox(var, 2);
	    break;
	  case K_WORLD_SIZE:
	    /* If the area is already set up, it's too late. */
	    if (area.width > 0 || area.height > 0)
	      break;
	    vary_world = TRUE;
	    /* Start with some defaults. */
	    new_circumference = DEFAULTCIRCUMFERENCE;
	    new_width = DEFAULTWIDTH;  new_height = DEFAULTHEIGHT;
	    new_latitude = 0;  new_longitude = 0;
	    /* If we have explicit defaults, use them. */
	    if (var->dflt != lispnil) {
		vartmp = var->dflt;
		new_width = c_number(eval(car(vartmp)));
		vartmp = cdr(vartmp);
		if (vartmp != lispnil) {
		    new_height = c_number(eval(car(vartmp)));
		    vartmp = cdr(vartmp);
		} else {
		    new_height = new_width;
		}
		if (vartmp != lispnil) {
		    new_circumference = c_number(eval(car(vartmp)));
		    vartmp = cdr(vartmp);
		}
		if (vartmp != lispnil) {
		    new_latitude = c_number(eval(car(vartmp)));
		    vartmp = cdr(vartmp);
		}
		if (vartmp != lispnil) {
		    new_longitude = c_number(eval(car(vartmp)));
		}
	    }
	    eval_tcl_cmd("set new_width %d", new_width);
	    eval_tcl_cmd("set new_height %d", new_height);
	    eval_tcl_cmd("set new_circumference %d", new_circumference);
	    eval_tcl_cmd("set new_latitude %d", new_latitude);
	    eval_tcl_cmd("set new_longitude %d", new_longitude);
	    break;
	  case K_REAL_TIME:
	    vary_real_time = TRUE;
	    /* Start with some defaults. */
	    new_time_for_game = new_time_per_side = new_time_per_turn = 0;
	    /* If we have explicit defaults, use them. */
	    if (var->dflt != lispnil) {
		vartmp = var->dflt;
		new_time_for_game = c_number(eval(car(vartmp)));
		vartmp = cdr(vartmp);
		if (vartmp != lispnil) {
		    new_time_per_side = c_number(eval(car(vartmp)));
		} else {
		    new_time_per_side = 0;
		}
		vartmp = cdr(vartmp);
		if (vartmp != lispnil) {
		    new_time_per_turn = c_number(eval(car(vartmp)));
		} else {
		    new_time_per_turn = 0;
		}
	    }
	    eval_tcl_cmd("set new_time_for_game %d", new_time_for_game);
	    eval_tcl_cmd("set new_time_per_side %d", new_time_per_side);
	    eval_tcl_cmd("set new_time_per_turn %d", new_time_per_turn);
	    break;
	  default:
	    if (numcheckboxes >= MAXCHECKBOXES) {
		init_warning("too many variants, can't set all of them");
		break;
	    }
	    interpret_checkbox(var, numcheckboxes);
	    ++numcheckboxes;
	    break;
	}
    }
    eval_tcl_cmd("set vary_world %d", vary_world);
    eval_tcl_cmd("set vary_real_time %d", vary_real_time);
}

static void
interpret_checkbox(Variant *var, int i)
{
    int newval;
    Obj *vartmp;

    eval_tcl_cmd("set variantstate(%d) active", i);
    eval_tcl_cmd("set varianttext(%d) \"%s\"", i, var->name);
    newval = FALSE;
    if (var->dflt != lispnil) {
	vartmp = eval(var->dflt);
	if (numberp(vartmp)) {
	    newval = c_number(vartmp);
	}
    }
    eval_tcl_cmd("set variantvalue(%d) %d", i, newval);
    checkboxes[i] = var;
    checkboxvalues[i] = newval;
}

static void
set_variant_value(int which, int val)
{
    checkboxvalues[which] = val;
}

static void
set_variant_world_size(int wid, int hgt, int circumf, int lat, int lon)
{
    new_width = wid;
    new_height = hgt;
    new_circumference = circumf;
    new_latitude = lat;
    new_longitude = lon;
}

static void
set_variant_real_time(int total, int perside, int perturn)
{
    new_time_for_game = total;
    new_time_per_side = perside;
    new_time_per_turn = perturn;
}

/* This is where we actually change the state of the game according to
   the variants.  Actually, the kernel does the changing, this just
   collects the variant values and passes them on. */

static void
implement_variants(void)
{
    int i;

    variants = lispnil;
    /* Implement the checkbox variants. */
    for (i = 0; i < numcheckboxes; ++i) {
	if (checkboxes[i]) {
	    push_int_binding(&variants, checkboxes[i]->id, checkboxvalues[i]);
	}
    }
    if (vary_world) {
	/* It is critically important that users not be able to
	   reshape already-alloced areas, but do let them know that
	   their request had to be overridden. */
	if (((area.width > 0 && area.width != new_width)
	     || (area.height > 0 && area.height != new_height)
	     || (world.circumference > 0
		 && world.circumference != new_circumference))
	    && (1 /* some layers (probably) allocated already */)) {
	    /* (this is misleading, is an "expected" alert) */
	    init_warning("Area dimensions must remain %d x %d, %d around world",
			 area.width, area.height, world.circumference);
	    new_width = area.width;  new_height = area.height;
	    new_circumference = world.circumference;
	}
	/* Make a world-size-setting and glue it into the list of variants. */
	push_key_cdr_binding(&variants, K_WORLD_SIZE, 
			     cons(new_number(new_width),
				  cons(new_number(new_height),
				       cons(new_number(new_circumference),
					    cons(new_number(new_longitude),
						 cons(new_number(new_latitude),
						      lispnil))))));
    }
    if (vary_real_time) {
	push_key_cdr_binding(&variants, K_REAL_TIME, 
			     cons(new_number(new_time_for_game),
				  cons(new_number(new_time_per_side),
				       cons(new_number(new_time_per_turn),
					    lispnil))));
    }
    do_module_variants(mainmodule, variants);
}

void
create_map(void)
{
    int u;
    Map *map;
    Side *side2;

    DGprintf("Creating map\n");
    map = (Map *) xmalloc(sizeof(Map));

    map->mode = move_mode;
    map->autoselect = TRUE;
    map->move_on_click = TRUE;

    map->see_all = dside->see_all;

    map->prefixarg = -1;
    map->inptype = NONUTYPE;

    /* Newest map goes on the front of the list. */
    map->next = dside->ui->maps;
    dside->ui->maps = map;

    dside->ui->curmap = map;

    eval_tcl_cmd("create_map_window");

    for_all_sides(side2) {
	update_side_display(dside, side2, TRUE);
    }
    for_all_unit_types(u) {
	last_num_units_in_play[u] = last_num_units_incomplete[u] = -1;
	init_unit_type_list(u);
	update_unit_type_list(u);
    }
    update_turn_display(dside, TRUE);

    set_tool_cursor(dside, map);
    eval_tcl_cmd("update_mode move");
}

static void enable_in_unit_type_list(Side *side, Map *map, int u, int flag);

static void
enable_in_unit_type_list(Side *side, Map *map, int u, int flag)
{
    eval_tcl_cmd("enable_unitlist %d %d", utype_indexes[u], flag);
}

/* Prompt for a type of a unit from player, maybe only allowing some types
   to be accepted.  Also allow specification of no unit type.  We do this
   by scanning the vector, building a string of chars and a vector of
   unit types, so as to be able to map back when done. */

int
ask_unit_type(Side *side, Map *map, char *prompt, int *possibles,
	      void (*handler)(Side *side, Map *map, int cancelled))
{
    int u, numtypes = 0;

    for_all_unit_types(u) {
	if (possibles == NULL || possibles[u]) {
	    map->uvec[numtypes] = u;
	    map->ustr[numtypes] = utype_name_n(u, 1)[0];
	    ++numtypes;
	    enable_in_unit_type_list(side, map, u, 1);
	} else {
	    enable_in_unit_type_list(side, map, u, -1);
	}
    }
    map->ustr[numtypes] = '\0';
    if (numtypes > 1) {
	eval_tcl_cmd("ask_unit_type_mode {%s [%s]}", prompt, map->ustr);
	map->modalhandler = handler;
    }
    return numtypes;
}

/* Do something with the char or unit type that the player entered. */

int
grok_unit_type(Side *side, Map *map, int *typep)
{
    int i, u;

    *typep = NONUTYPE;
    if (map->inptype != NONUTYPE) {
	*typep = map->inptype;
	/* Reset so doesn't affect subsequent unit type queries. */
	map->inptype = NONUTYPE;
    } else if (map->inpch != '\0') {
	if (map->inpch == '?') {
	    help_unit_type(side, map);
	    return FALSE;
	}
	i = iindex(map->inpch, map->ustr);
	if (i >= 0) {
	    *typep = map->uvec[i];
	} else {
	    notify(side, "Must type a unit type char from the list, or <esc>");
	    return FALSE;
	}
    } else {
	notify(side, "weird");
	return FALSE;
    }
    eval_tcl_cmd("ask_unit_type_done");
    /* Reset the appearance of the unit type list. */
    for_all_unit_types(u) {
	enable_in_unit_type_list(side, map, u, 0);
    }
    /* Make the unit type string be empty. */
    map->ustr[0] = '\0';
    return TRUE;
}

void
cancel_unit_type(Side *side, Map *map)
{
    int u;

    /* Reset the appearance of the unit type list. */
    for_all_unit_types(u) {
	enable_in_unit_type_list(side, map, u, 0);
    }
}

static void
help_unit_type(Side *side, Map *map)
{
    int i;
    char helpbuf[BUFSIZE];

    helpbuf[0] = '\0';
    for (i = 0; map->ustr[i] != '\0'; ++i) {
	/* Put out several types on each line. */
	if (i % 4 == 0) {
	    if (i > 0) {
		notify(side, "%s", helpbuf);
	    }
	    /* Indent each line a bit (also avoids notify's
	       auto-capitalization). */
	    strcpy(helpbuf, "  ");
	}
	tprintf(helpbuf, "%c %s, ", map->ustr[i], u_type_name(map->uvec[i]));
    }
    /* Add an extra helpful comment, then dump any leftovers. */
    tprintf(helpbuf, "? for this help info"); 
    notify(side, "%s", helpbuf);
}

int
ask_terrain_type(Side *side, Map *map, char *prompt, int *possibles,
		 void (*handler)(Side *side, Map *map, int cancelled))
{
    int numtypes = 0, t;

    for_all_terrain_types(t) {
	if (possibles == NULL || possibles[t]) {
	    map->tvec[numtypes] = t;
	    map->tstr[numtypes] =
	      (!empty_string(t_char(t)) ? t_char(t)[0] : (t - 'a'));
	    ++numtypes;
	}
    }
    map->tstr[numtypes] = '\0';
    if (numtypes > 1) {
	eval_tcl_cmd("ask_terrain_type_mode {%s [%s]}", prompt, map->tstr);
	map->modalhandler = handler;
    }
    return numtypes;
}

/* Do something with the char or terrain type that the player entered. */

int
grok_terrain_type(Side *side, Map *map, int *typep)
{
    int i;

    *typep = NONTTYPE;
    if (map->inpch == '?') {
	help_terrain_type(dside, map);
	return FALSE;
    }
    i = iindex(map->inpch, map->tstr);
    if (i >= 0) {
	*typep = map->tvec[i];
	eval_tcl_cmd("ask_terrain_type_done");
	return TRUE;
    } else {
	notify(dside, "Must type a terrain type char or <esc>");
	return FALSE;
    }
}

static void
help_terrain_type(Side *side, Map *map)
{
    int i;
    char helpbuf[BUFSIZE];

    for (i = 0; map->tstr[i] != '\0'; ++i) {
	/* Put out several types on each line. */
	if (i % 4 == 0) {
	    if (i > 0) {
		notify(side, "%s", helpbuf);
	    }
	    /* Indent each line a bit (also avoids confusion due to
	       notify's capitalization). */
	    strcpy(helpbuf, "  ");
	}
	tprintf(helpbuf, "%c %s, ", map->tstr[i], t_type_name(map->tvec[i]));
    }
    /* Add an extra helpful comment, then dump any leftovers. */
    tprintf(helpbuf, "? for this help info"); 
    notify(side, "%s", helpbuf);
}

/* User is asked to pick a position on map.  This will iterate until the
   space bar designates the final position. */

/* (should change the cursor temporarily) */

void
ask_position(Side *side, Map *map, char *prompt,
	     void (*handler)(Side *side, Map *map, int cancel))
{
    eval_tcl_cmd("ask_position_mode {%s [click to set]}", prompt);
    map->answer[0] = '\0';
    map->modalhandler = handler;
}

int
grok_position(Side *side, Map *map, int *xp, int *yp, Unit **unitp)
{
    if (in_area(map->inpx, map->inpy)) {
	*xp = map->inpx;  *yp = map->inpy;
	if (unitp != NULL)
	  *unitp = map->inpunit;
	eval_tcl_cmd("ask_position_done");
	return TRUE;
    } else {
	/* Make any possible usage attempts fail. */
	*xp = *yp = -1;
	if (unitp != NULL)
	  *unitp = NULL;
	return FALSE;
    }
}

/* Prompt for a yes/no answer with a settable default. */

void
ask_bool(Side *side, Map *map, char *question, int dflt,
	 void (*handler)(Side *side, Map *map, int cancelled))
{
    eval_tcl_cmd("ask_bool_mode {%s} %d", question, dflt);
    map->answer[0] = '\0';
    map->tmpint = dflt;
    map->modalhandler = handler;
}

/* Figure out what the answer actually is, keeping the default in mind. */

int
grok_bool(Side *side, Map *map)
{
    int dflt = map->tmpint;
    char ch = map->inpch;

    if (dflt ? (lowercase(ch) == 'n') : (lowercase(ch) == 'y'))
      dflt = !dflt;
    eval_tcl_cmd("ask_bool_done");
    return dflt;
}

/* Read a string from the prompt window.  Deletion is allowed, and a
   text cursor (an underscore) is displayed. */

void
ask_string(Side *side, Map *map, char *prompt, char *dflt,
	   void (*handler)(Side *side, Map *map, int cancelled))
{
    /* Default must be non-NULL. */
    if (dflt == NULL)
      dflt = "";
    sprintf(map->answer, "%s", dflt);
    eval_tcl_cmd("ask_string_mode {%s} {%s}", prompt, map->answer);
    map->modalhandler = handler;
}

/* Dig a character from the input and add it into the string.
   Keep returning FALSE until we get something, then make a copy
   of the result string and return TRUE. */

int
grok_string(Side *side, Map *map, char **strp)
{
    char ch = map->inpch;
    int len;

    if (ch == '\r' || ch == '\n') {
	*strp = copy_string(map->answer);
	eval_tcl_cmd("ask_string_done");
	return TRUE;
    } else {
	len = strlen(map->answer);
	if (ch == BACKSPACE_CHAR || ch == DELETE_CHAR) {
	    if (len > 0)
	      --len;
	} else {
	    map->answer[len++] = ch;
	}
	map->answer[len] = '\0';
	eval_tcl_cmd("update_string_mode \"%s\"", map->answer);
	return FALSE;
    }
}

void
ask_side(Side *side, Map *map, char *prompt, Side *dfltside,
	 void (*handler)(Side *side, Map *map, int cancelled))
{
    char *dfltstr;

    dfltstr = (dfltside == NULL ? "nobody" : side_name(dfltside));
    sprintf(map->answer, "%s", dfltstr);
    eval_tcl_cmd("ask_side_mode {%s} {%s}", prompt, map->answer);
    map->modalhandler = handler;
}

int
grok_side(Side *side, Map *map, Side **side2p)
{
    char ch = map->inpch;
    int len;
    Side *side3;

    *side2p = NULL;
    if (ch == '\r' || ch == '\n') {
	if (empty_string(map->answer)
	    || strcmp(map->answer, "nobody") == 0) {
	    eval_tcl_cmd("ask_side_done");
	    return TRUE;
	}
	for_all_sides(side3) {
	    if (!empty_string(side3->name)
		&& strcmp(map->answer, side3->name) == 0) {
		*side2p = side3;
		eval_tcl_cmd("ask_side_done");
		return TRUE;
	    }
	    if (!empty_string(side3->noun)
		&& strcmp(map->answer, side3->noun) == 0) {
		*side2p = side3;
		eval_tcl_cmd("ask_side_done");
		return TRUE;
	    }
	    if (!empty_string(side3->adjective)
		&& strcmp(map->answer, side3->adjective) == 0) {
		*side2p = side3;
		eval_tcl_cmd("ask_side_done");
		return TRUE;
	    }
	}
	beep(side);
	return FALSE;
    } else {
	len = strlen(map->answer);
	if (ch == BACKSPACE_CHAR || ch == DELETE_CHAR) {
	    if (len > 0)
	      --len;
	} else {
	    map->answer[len++] = ch;
	}
	map->answer[len] = '\0';
	eval_tcl_cmd("update_side_mode \"%s\"", map->answer);
	return FALSE;
    }
}
