/* A minimal interface to Xconq.
   Copyright (C) 1991-1997, 1999-2000 Stanley T. Shebs.

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

/* This interface uses only what is required by ANSI, so it should run
   just about everywhere without any changes.  This is most useful for
   testing the kernel and game design libraries.

   This file can also serve as a starting point for writing a new
   interface, since it has simple or default implementations of the
   routines that any interface has to support. */

#include "conq.h"
#include "kpublic.h"
#include "print.h"
#include "cmdline.h"

static void init_displays(void);
static void get_input(void);
static void interpret_command(Obj *cmd);
static void list_one_unit(Unit *unit);
static void interpret_help(Side *side, char *str);
static int do_cmd(Side *side, Obj *cmd, Obj *parms);
#ifdef DEBUGGING
static void toggle_debug(Side *side, Obj *cmd, Obj *parms);
static void toggle_debugg(Side *side, Obj *cmd, Obj *parms);
static void toggle_debugm(Side *side, Obj *cmd, Obj *parms);
#endif /* DEBUGGING */
static void list_sides(Side *side, Obj *cmd, Obj *parms);
static void list_units(Side *side, Obj *cmd, Obj *parms);
static void list_actors(Side *side, Obj *cmd, Obj *parms);
static void list_cells(Side *side, Obj *cmd, Obj *parms);
static void do_task_cmd(Side *side, Obj *cmd, Obj *parms);
static void do_finish_turn(Side *side, Obj *cmd, Obj *parms);
static void do_finish_all(Side *side, Obj *cmd, Obj *parms);
static void do_repeat(Side *side, Obj *cmd, Obj *parms);
static void do_multiple(Side *side, Obj *cmd, Obj *parms);
static void do_free_run(Side *side, Obj *cmd, Obj *parms);
static void do_save(Side *side, Obj *cmd, Obj *parms);
static void do_eval(Side *side, Obj *cmd, Obj *parms);
static void do_help(Side *side, Obj *cmd, Obj *parms);
static void do_print(Side *side, Obj *cmd, Obj *parms);
static void do_memory(Side *side, Obj *cmd, Obj *parms);
static void do_quit(Side *side, Obj *cmd, Obj *parms);
static int do_action(Side *side, Unit *unit, Obj *cmd, Obj *args);
static void show_help(Side *side, HelpNode *node);
static void describe_commands(int arg, char *key, TextBuffer *buf);

#ifdef THINK_C
/* This is to get the command line reader in Think C on the Mac. */
#include <console.h>
#endif /* THINK_C */

/* This structure maintains state that is local to a side's display.
   At the very least, it must track when the display is open and closed. */

typedef struct a_ui {
    int active;
} UI;

Side *defaultside = NULL;

HelpNode *curhelpnode;

int freerunturns = 0;

int repetition = 0;

Obj *multicmd = NULL;

time_t skelturnstart;

int numcellupdatesperturn = 0;

int numusefulcellupdatesperturn = 0;

/* The main program just calls the setup routines, then enters an infinite
   loop interpreting input and running the simulation. */

int
main(int argc, char *argv[])
{
    extern long initrandstate, randstate;
    long currandstate;

#ifdef THINK_C
    /* This is how Think C picks up a command line. */
    argc = ccommand(&argv);
#endif
    printf("Skeleton Xconq version %s\n", version_string());
    printf("(C) %s\n", copyright_string());
    init_library_path(NULL);
    clear_game_modules();
#ifdef DEBUGGING
    init_debug_to_stdout();
#endif /* DEBUGGING */
    init_data_structures();
    /* Dump the random state so we can reproduce the run if necessary. */
    printf("Random state is %ld", randstate);
    if (initrandstate != randstate) {
	printf(" (seed was %ld)", initrandstate);
    }
    printf("\n");
    currandstate = randstate;
    parse_command_line(argc, argv, general_options);
    if (currandstate != randstate) {
	printf("Random state is now %ld.\n", randstate);
    }
    load_all_modules();
    /* See if we have something resembling a valid game.  A synth method might
       still change some numbers, but it's up to the method to do it right. */
    check_game_validity();
    parse_command_line(argc, argv, variant_options);
    set_variants_from_options();
    parse_command_line(argc, argv, player_options);
    set_players_from_options();
    parse_command_line(argc, argv, leftover_options);
    make_trial_assignments();
    calculate_globals();
    run_synth_methods();
    final_init();
    assign_players_to_sides();
    init_signal_handlers();
    time(&skelturnstart);
    run_game(0);
    init_displays();
    multicmd = lispnil;
    while (1) {
	if (freerunturns > 0) {
	    if (probability(10)) {
	    	printf("No apparent progress, forcing the turn to finish.\n");
	    	do_finish_all(NULL, lispnil, lispnil);
	    }
	    if (endofgame)
	      freerunturns = 0;
	} else {
	    get_input();
	}
	/* We stay in here until nothing more to do. */
	run_local_ai(1, 20);
	while (run_game(99) > 0) {
	    run_local_ai(2, 20);
	}
    }
}

Player *
add_default_player(void)
{
    Player *dflt = add_player();

    dflt->displayname = "stdio";
    Dprintf("Added the default player %s\n", player_desig(dflt));
    return dflt;
}

void
make_default_player_spec(void)
{
    default_player_spec = "stdio";
}

/* This routine handles all the displays that might need to be opened. */
/* (Note that skelconq may have multiple "displays" open.) */

static void
init_displays(void)
{
    Side *side;

    for_all_sides(side) {
	if (side_has_display(side)) {
	    side->ui->active = TRUE;
	    printf("%s now has an open display.\n", side_desig(side));
	}
    }
}

/* Create a user interface, but leave it turned off. */

void
init_ui(Side *side)
{
    if (side_wants_display(side)) {
        side->ui = (UI *) xmalloc(sizeof(UI));
	/* Display should not become active yet. */
	side->ui->active = FALSE;
	defaultside = side;
	/* Do this so game doesn't run out of control. */
	net_set_autofinish(side, FALSE);
	DGprintf("Created a UI for %s\n", side_desig(side));
    } else {
	side->ui = NULL;
    }
}

/* This tests whether the side has a display and if it is in use. */

int
active_display(Side *side)
{
    return (side && side_has_display(side) && side->ui->active);
}

/* Input reading waits for a number of sides, possibly times out. */

static void
get_input(void)
{
    int cmdlineno = 1, endlineno = 1;
    Obj *cmd;
    Side *side = NULL;

    if (realtime_game()) {
	for_all_sides(side) {
	    update_clock_display(side, TRUE);
	}
    }
    /* (should say which sides we're waiting for) */
    printf("> ");
    fflush(stdout);
    if (repetition-- > 0) {
	cmd = multicmd;
    } else {
	cmd = read_form(stdin, &cmdlineno, &endlineno);
    }
    if (cmd != lispeof) {
	Dprintlisp(cmd);
	interpret_command(cmd);
    } else {
	printf("EOF reached\n");
	/* should just close one display, leave others running */
	exit(0);
    }
}

Unit *thisunit;

/* Do some simple command parsing, just enough to exercise the program. */

static void
interpret_command(Obj *origcmd)
{
    char *str;
    Obj *cmd, *verb = lispnil, *parms = lispnil;
    Side *side = NULL;
    Unit *unit = NULL;

    thisunit = NULL;
    cmd = origcmd;
    if (consp(cmd) && numberp(car(cmd))) {
	side = side_n(c_number(car(cmd)));
	cmd = cdr(cmd);
    }
    if (consp(cmd) && numberp(car(cmd))) {
	unit = find_unit(c_number(car(cmd)));
	thisunit = unit;
	cmd = cdr(cmd);
    }
    if (consp(cmd)) {
	verb = car(cmd);
	parms = cdr(cmd);
    } else if (symbolp(cmd)) {
	verb = cmd;
    }
    if (verb == lispnil) {
    } else if (do_cmd(side, verb, parms)) {
    } else if (do_action(side, unit, verb, parms)) {
    } else if (symbolp(verb) && *(str = c_string(verb)) == '?') {
	interpret_help(side, str+1);
    } else {
	printf("Command ");
	fprintlisp(stdout, origcmd);
	printf(" not understood, ignoring it\n");
    }
}

/* Random commands. */

static void
toggle_debug(Side *side, Obj *cmd, Obj *parms)
{
    if (parms == lispnil) {
	Debug = !Debug;
    } else if (symbolp(car(parms)) && equal(car(parms), intern_symbol("on"))) {
	Debug = TRUE;
    } else {
	Debug = FALSE;
    }
}

static void
toggle_debugm(Side *side, Obj *cmd, Obj *parms)
{
    if (parms == lispnil) {
	DebugM = !DebugM;
    } else if (symbolp(car(parms)) && equal(car(parms), intern_symbol("on"))) {
	DebugM = TRUE;
    } else {
	DebugM = FALSE;
    }
}

static void
toggle_debugg(Side *side, Obj *cmd, Obj *parms)
{
    if (parms == lispnil) {
	DebugG = !DebugG;
    } else if (symbolp(car(parms)) && equal(car(parms), intern_symbol("on"))) {
	DebugG = TRUE;
    } else {
	DebugG = FALSE;
    }
}

static void
list_sides(Side *side, Obj *cmd, Obj *parms)
{
    int u;
    Side *side2;
    Agreement *ag;
    extern int num_agreements;

    printf("Sides: (%s)\n",
	   (g_use_side_priority() ? "sequential" : "simultaneous"));
    for_all_sides(side2) {
	printf("%s played by %s",
	       side_desig(side2), player_desig(side2->player));
	printf(", autofinish %d", side2->autofinish);
	if (g_use_side_priority())
	  printf(", priority %d", side2->priority);
	printf(", %s", (side2->finishedturn ? "finished" : "moving"));
	printf("\n");
	if (using_tech_levels()) {
	    printf("Tech:");
	    for_all_unit_types(u) {
		if (u_tech_max(u) > 0) {
		    printf("  %s %d/%d",
			   u_type_name(u), side2->tech[u], u_tech_max(u));
		}
	    }
	    printf("\n");
	}
	/* (should say something about mplayer goals here) */
    }
    if (num_agreements > 0) {
	printf("Agreements:\n");
	for_all_agreements(ag) {
	    printf("%s\n", agreement_desig(ag));
	}
    }
}

static void
list_units(Side *side, Obj *cmd, Obj *parms)
{
    Unit *unit;

    printf("Units:\n");
    for_all_units(unit) {
	list_one_unit(unit);
    }
}

static void
list_actors(Side *side, Obj *cmd, Obj *parms)
{
    int i;
    Side *side2;
    Unit *unit;

    printf("Actors (listed by side):\n");
    for_all_sides(side2) {
	printf("%s: %s\n",
	       side_desig(side2),
	       (side2->finishedturn ? "(finished)" : ""));
    }
    for_all_sides(side2) {
	for (i = 0; i < side2->actionvector->numunits; ++i) {
	    unit = unit_in_vector(side2->actionvector, i);
	    list_one_unit(unit);
	}
    }
}

static void
list_one_unit(Unit *unit)
{
    char status[BUFSIZE];

    if (unit == NULL) {
	printf("  -\n");
	return;
    }
    if (!completed(unit)) {
	sprintf(status, " cp %d ", unit->cp);
    } else if (unit->hp < u_hp(unit->type)) {
	sprintf(status, " hp %d ", unit->hp);
    } else {
	sprintf(status, " ");
    }
    printf("  %s%s%s %s\n",
	   unit_desig(unit), status,
	   actorstate_desig(unit->act), plan_desig(unit->plan));
}

static void
list_cells(Side *side, Obj *cmd, Obj *parms)
{
    int x = c_number(car(parms)), y = c_number(cadr(parms));

    printf("At %d,%d", x, y);
    if (in_area(x, y)) {
	printf(", terrain %s", t_type_name(terrain_at(x, y)));
	/* (dump borders?) */
	printf(", elev %d", elev_at(x, y));
	printf(", temp %d", temperature_at(x, y));
	/* (etc) */
    } else {
	printf(" - outside area!");
    }
    printf("\n");
}

static void
do_task_cmd(Side *side, Obj *cmd, Obj *parms)
{
    int i, j, numargs;
    char *taskname;
    Obj *tasksym = car(parms), *taskparms = cdr(parms);
    Task *task;

    if (symbolp(tasksym)) {
	taskname = c_string(tasksym);
	/* Iterate through task names looking for a match. */
	for (i = 0; taskdefns[i].name != NULL; ++i) {
	    if (strcmp(taskname, taskdefns[i].name) == 0) {
		if (thisunit != NULL && thisunit->plan != NULL) {
		    task = create_task(i);
		    numargs = strlen(taskdefns[i].argtypes);
		    for (j = 0; j < numargs; ++j) {
			if (taskparms != lispnil) {
			    task->args[j] = c_number(car(taskparms));
			    taskparms = cdr(taskparms);
			} else {
			    task->args[j] = 0;
			}
		    }
		    net_add_task(thisunit, 0, task);
		}
		return;
	    }
	}
	fprintf(stderr, "Task type \"%s\" not recognized\n", taskname);
    }
}

static void
do_finish_turn(Side *side, Obj *cmd, Obj *parms)
{
    net_finish_turn(side ? side : defaultside);
}

static void
do_finish_all(Side *side, Obj *cmd, Obj *parms)
{
    Side *side2;

    for_all_sides(side2) {
	net_finish_turn(side2);
    }
}

static void
do_free_run(Side *side, Obj *cmd, Obj *parms)
{
    Side *side2;

    if (consp(parms) && numberp(car(parms))) {
	freerunturns = c_number(car(parms));
    } else {
	freerunturns = 1;
    }
    for_all_sides(side2) {
	net_set_autofinish(side2, TRUE);
    }
}

static void
do_repeat(Side *side, Obj *cmd, Obj *parms)
{
}

static void
do_multiple(Side *side, Obj *cmd, Obj *parms)
{
    repetition = c_number(car(parms));
    multicmd = cdr(parms);
}

static void
do_save(Side *side, Obj *cmd, Obj *parms)
{
    if (!write_entire_game_state(saved_game_filename())) {
	fprintf(stderr, "Save failed.\n");
    }
}

static void
do_eval(Side *side, Obj *cmd, Obj *parms)
{
    interp_form(NULL, car(parms));
}

static void
do_help(Side *side, Obj *cmd, Obj *parms)
{
    printf("To look at help topics, type \"?<letter>\",\n");
    printf("where 'n' and 'p' go to next and previous nodes\n");
}

static void
do_print(Side *side, Obj *cmd, Obj *parms)
{
    char *fname;
    FILE *fp;

    if (parms != lispnil) {
	if (stringp(car(parms))) {
	    fname = c_string(car(parms));
	    if ((fp = fopen(fname, "w")) != NULL) {
		print_game_description_to_file(fp);
		fclose(fp);
	    } else {
		fprintf(stderr, "couldn't open \"%s\"\n", fname);
	    }
	} else {
	    /* error, not a string */
	}
    } else {
	print_game_description_to_file(stdout);
    }
}

static void
do_memory(Side *side, Obj *cmd, Obj *parms)
{
    extern int grandtotmalloc;

    printf("%d bytes allocated.\n", grandtotmalloc);
}

/* Exit immediately, no questions asked. */

static void
do_quit(Side *side, Obj *cmd, Obj *parms)
{
    /* A quit command of the form "(quit if-end)" only actually quits if the
       game is over. */
    if (symbolp(car(parms)) && equal(car(parms), intern_symbol("if-end"))) {
	if (!endofgame)
	  return;
    }
    printf("Quitting.\n");
    exit(0);
}

struct a_cmd {
    char *cmd;
    void (*fn)(Side *side, Obj *cmd, Obj *parms);
} cmdtable[] = {
    { "debug", toggle_debug },
    { "debugm", toggle_debugm },
    { "debugg", toggle_debugg },
    { "sides", list_sides },
    { "units", list_units },
    { "actors", list_actors },
    { "cells", list_cells },
    { "task", do_task_cmd },
    { "fin", do_finish_turn },
    { "finall", do_finish_all },
    { "again", do_repeat },
    { "*", do_multiple },
    { "run", do_free_run },
    { "save", do_save },
    { "eval", do_eval },
    { "help", do_help },
    { "print", do_print },
    { "memory", do_memory },
    { "quit", do_quit },
    { NULL, NULL }
};

/* Try to find and execute an arbitrary command. */

static int
do_cmd(Side *side, Obj *cmd, Obj *parms)
{
    struct a_cmd *cmdentry = cmdtable;
    char *cmdstr;

    if (!symbolp(cmd))
      return FALSE;
    cmdstr = c_string(cmd);
    while (cmdentry->cmd != NULL) {
	if (strcmp(cmdstr, cmdentry->cmd) == 0) {
	    (*(cmdentry->fn))(side, cmd, parms);
	    return TRUE;
	}
	++cmdentry;
    }
    return FALSE;
}

static int
do_action(Side *side, Unit *unit, Obj *cmd, Obj *args)
{
    int randomact = FALSE;
    ActionDefn *actdefn = actiondefns;
    char *cmdstr, *argstr;
    char localbuf[BUFSIZE];
    int i = 0, rslt;
    Obj *rest;
    Action action;

    if (!symbolp(cmd))
      return FALSE;
    cmdstr = c_string(cmd);
    if (side == NULL) {
	side = defaultside;
	if (side == NULL) {
	    fprintf(stderr, "Using first side since no defaults avail\n");
	    side = sidelist->next;
	}
    }
    if (unit == NULL) {
	/* (think of something to do here) */
    }
    while (actdefn->name != NULL) {
	if (strcmp(cmdstr, actdefn->name) == 0) {
	    memset(&action, 0, sizeof(Action));
	    action.type = actdefn->typecode;
	    /* Special option to generate random args to action. */
	    if (symbolp(car(args))
		&& strcmp("randomly", c_string(car(args))) == 0) {
		args = cdr(args);
		randomact = TRUE;
	    }
	    if (unit == NULL) {
		if (randomact) {
		    while (!((unit = find_unit(xrandom(numunits))) != NULL
			     && (unit->act != NULL || flip_coin()))
			   && probability(99));
		    if (unit == NULL) {
			fprintf(stderr, "Can't find a unit to act!\n");
			/* We're out of luck, just give up. */
			return TRUE;
		    }
		} else {
		    fprintf(stderr, "No unit to %s!\n", cmdstr);
		    /* *Command* *was* valid, just the args were bad. */
		    return TRUE;
		}
	    }
	    /* Move args from list into action. */
	    argstr = actdefn->argtypes;
	    for (rest = args; rest != lispnil; rest = cdr(rest)) {
		if (argstr[i] != '\0') {
		    action.args[i] = c_number(car(rest));
		} else {
		    break;
		}
		++i;
	    }
	    if (i != strlen(argstr)) {
		if (randomact) {
		    make_plausible_random_args(argstr, i, &(action.args[0]),
					       unit);
		} else {
		    printf("Mismatched args!\n");
		    return TRUE;
		}
	    }
	    sprintf(localbuf, "%s tries %s",
		    unit_desig(unit), action_desig(&action));
	    rslt = execute_action(unit, &action);
	    printf("%s - %s\n", localbuf, hevtdefns[rslt].name);
	    return TRUE;
	}
	++actdefn;
    }
    return FALSE;
}

/* Shut down displays - should be done before any sort of exit. */

void
close_displays(void)
{
    Side *side;

    for_all_sides(side) {
	if (active_display(side)) {
	    side->ui->active = FALSE;
	    printf("Display \"%s\" closed.\n", side->player->displayname);
	}
    }
}

void
low_notify(Side *side, char *str)
{
    printf("To %s: %s\n", side_desig(side), str);
}

void
update_area_display(Side *side)
{
}

/* (should count # of calls to inactive and non-displayed side...) */

void
update_cell_display(Side *side, int x, int y, int flags)
{
    ++numcellupdatesperturn;
    if (active_display(side)) {
	++numusefulcellupdatesperturn;
	DGprintf("Update %s: view of %d,%d 0x%x\n",
		 side_desig(side), x, y, flags);
    }
}

/* This hook updates any display of the current turn/date, and any
   other global info, as needed. */

void
update_turn_display(Side *side, int rightnow)
{
    long secs;
    time_t xxx;

    if (active_display(side)) {
	time(&xxx);
	secs = idifftime(xxx, skelturnstart);
	printf("Update %s: turn %d (%ld seconds since last)",
	       side_desig(side), g_turn(), secs);
	DGprintf("%s", (rightnow ? " (now)" : ""));
	printf("\n");
	printf("%d cell updates, %d useful\n",
	       numcellupdatesperturn, numusefulcellupdatesperturn);
	numcellupdatesperturn = numusefulcellupdatesperturn = 0;
	skelturnstart = xxx;
	--freerunturns;
	if (freerunturns == 0) {
	    Side *side2;

	    for_all_sides(side2) {
	        if (active_display(side2))
		  net_set_autofinish(side2, FALSE);
	    }
	}
	if (rightnow)
	  fflush(stdout);
    }
}

void
update_action_display(Side *side, int rightnow)
{
    if (active_display(side) && DebugG) {
	printf("Update %s: ready to act\n", side_desig(side));
    }
}

void
update_action_result_display(Side *side, Unit *unit, int rslt, int rightnow)
{
    if (active_display(side) && DebugG) {
	printf("Update %s: %s action result is %s\n",
	       side_desig(side), unit_desig(unit), hevtdefns[rslt].name);
    }
}

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

void
update_fire_at_display(Side *side, Unit *unit, Unit *unit2, int m, int rightnow)
{
    if (active_display(side) && DebugG) {
	printf("Update %s: %s fire at %s\n",
	       side_desig(side), unit_desig(unit), unit_desig(unit2));
    }
}

/* 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)
{
    if (active_display(side) && DebugG) {
	printf("Update %s: %s fire into %d,%d\n",
	       side_desig(side), unit_desig(unit), x, y);
    }
}

void
update_event_display(Side *side, HistEvent *hevt, int rightnow)
{
    if (active_display(side)) {
	switch (hevt->type) {
	  case H_SIDE_LOST:
	    printf("%s lost!\n", side_desig(side_n(hevt->data[0])));
	    break;
	  case H_SIDE_WON:
	    printf("%s won!\n",  side_desig(side_n(hevt->data[0])));
	    break;
	  default:
	    DGprintf("Update %s: event %s %d\n",
		     side_desig(side), hevtdefns[hevt->type].name,
		     hevt->data[0]);
	}
    }
}

void
update_all_progress_displays(char *str, int s)
{
    if (DebugG) {
	printf("Update all progress displays\n");
    }
}

/* This hook should update the side's view of the given side, no matter
   who it belongs to. */

void
update_side_display(Side *side, Side *side2, int rightnow)
{
    char *side2desc = copy_string(side_desig(side2));

    if (active_display(side) && DebugG) {
	printf("Update %s: side %s%s\n",
	       side_desig(side), side2desc, (rightnow ? " (now)" : ""));
    }
    if (numatypes > 0
	&& g_side_can_research()
	&& side == side2
	&& side->research_topic == NOADVANCE) {
	/* Should eventually pop up a side research dialog instead. */
	auto_pick_side_research(side);
    }
}

/* This hook should update the side's view of the given unit, no matter
   who it belongs to. */

void
update_unit_display(Side *side, Unit *unit, int rightnow)
{
    if (active_display(side) && DebugG) {
	printf("Update %s: unit %s%s\n",
	       side_desig(side), unit_desig(unit), (rightnow ? " (now)" : ""));
    }
}

void
update_unit_acp_display(Side *side, Unit *unit, int rightnow)
{
    if (active_display(side) && DebugG) {
	printf("Update %s: unit %s acp%s\n",
	       side_desig(side), unit_desig(unit), (rightnow ? " (now)" : ""));
    }
}

/* This hook updates any realtime clock displays.  If the game does not
   have any realtime constraints, this will never be called. */

void
update_clock_display(Side *side, int rightnow)
{
    if (active_display(side) && DebugG) {
	printf("Update %s: %d secs this turn, %d total\n",
	       side_desig(side), side->turntimeused, side->totaltimeused);
	/* also display total game clock */
    }
}

void
update_message_display(Side *side, Side *sender, char *str, int rightnow)
{
    if (active_display(side) && DebugG) {
	printf("Update %s: side %d sends \"%s\"%s\n",
	       side_desig(side), side_number(sender), str, (rightnow ? " (now)" : ""));
    }
}

void
update_everything(void)
{
    printf("Update everything!\n");
}

void
action_point(Side *side, int x, int y)
{
}


/* Generate a description of all the user input that is possible. */

static void
describe_commands(int arg, char *key, TextBuffer *buf)
{
    struct a_cmd *cmdentry;

    for (cmdentry = cmdtable; cmdentry->cmd != NULL; ++cmdentry) {
	tbcat(buf, cmdentry->cmd);
	tbcat(buf, "\n");
    }
}

static void
interpret_help(Side *side, char *topic)
{
    HelpNode *node;

    if (side == NULL)
      side = defaultside;
    if (side == NULL) {
	fprintf(stderr, "no side to help?\n");
	return;
    }
    if (curhelpnode == NULL) {
	add_help_node("commands", describe_commands, 0, first_help_node);
	curhelpnode = first_help_node;
    }
    switch (topic[0]) {
      case 'n':
	curhelpnode = curhelpnode->next;
	break;
      case 'p':
	curhelpnode = curhelpnode->prev;
	break;
      case 'a':
	for (node = first_help_node->next; node != first_help_node; node = node->next) {
	    show_help(side, node);
	}
	return; /* don't show cur help node too */
      case '\0':
	/* Note that no topic char equals '\0', so will come here. */
      default:
	curhelpnode = first_help_node;
	break;
    }
    show_help(side, curhelpnode);
}

/* Spew out the entire text of the current help node. */

static void
show_help(Side *side, HelpNode *helpnode)
{
    int linelen, skipchar;
    char *linebegin = get_help_text(helpnode), *lineend;

    printf("Topic: %s\n", helpnode->key);
    while (*linebegin != '\0') {
	skipchar = 0;
	lineend = (char *) strchr(linebegin, '\n');
	if (lineend)
	  skipchar = 1;
	linelen = (lineend ? lineend - linebegin : strlen(linebegin));
	if (linelen > 75)
	  linelen = 75;
	strncpy(spbuf, linebegin, linelen);
	spbuf[linelen] = '\0';
	printf("%s\n", spbuf);
	linebegin += linelen + skipchar;
    }
}

/* This reports progress in reading GDL files. */

void
announce_read_progress(void)
{
}

/* This is used for initialization steps that take a long time. */

int linemiddle = FALSE;

void
announce_lengthy_process(char *msg)
{
    printf("%s; ", msg);
    fflush(stdout);
    linemiddle = TRUE;
}

/* Update the progress, expressing it as a percentage done. */

void
announce_progress(int percentdone)
{
    printf(" %d%%", percentdone);
    fflush(stdout);
    linemiddle = TRUE;
}

/* Announce the end of the lengthy process. */

void
finish_lengthy_process(void)
{
    printf(" done.\n");
    linemiddle = FALSE;
}

int
schedule_movie(Side *side, char *movie, ...)
{
    return FALSE;
}

void
play_movies(SideMask sidemask)
{
}

void
flush_display_buffers(Side *side)
{
    if (active_display(side) && DebugG) {
	printf("To %s: flush display buffers\n", side_desig(side));
    }
}

/* An init error needs to have the command re-run. */

void
low_init_error(char *str)
{
    if (linemiddle)
      printf("\n");
    fprintf(stderr, "Error: %s.\n", str);
    fflush(stderr);
}

/* A warning just gets displayed, no other action is taken. */

void
low_init_warning(char *str)
{
    if (linemiddle)
      printf("\n");
    fprintf(stdout, "Warning: %s.\n", str);
    fflush(stdout);
}

/* A run error is fatal. */

void
low_run_error(char *str)
{
    if (linemiddle)
      fprintf(stderr, "\n");
    fprintf(stderr, "Error: %s.\n", str);
    fflush(stderr);
    exit(1);
}

/* Runtime warnings are for when it's important to bug the players,
   usually a problem with Xconq or a game design. */

void
low_run_warning(char *str)
{
    if (linemiddle)
      printf("\n");
    fprintf(stdout, "Warning: %s.\n", str);
    fflush(stdout);
}

void
print_form(Obj *form)
{
    print_form_and_value(stdout, form);
}

void
end_printing_forms(void)
{
}

/* Note that the fake declaration below will cause problems if imf.h
   is included. */

void make_generic_image_data(char *imf);
void
make_generic_image_data(char *imf)
{
}

#ifdef MAC

int current_cursor;
int receivecursor;
int sendcursor;

int
serial_port_dialog()
{
    return 0;
}

#endif /* MAC */

void
unit_research_dialog(Unit *unit)
{
    auto_pick_unit_research(unit);
}

void
add_remote_locally(int rid, char *str)
{
}

void
send_chat(int rid, char *str)
{
    printf("From %d: received chat \"%s\"\n", rid, str);
}
