Initial revision
authorEric S. Raymond <esr@thyrsus.com>
Thu, 1 Dec 1994 09:56:26 +0000 (09:56 +0000)
committerEric S. Raymond <esr@thyrsus.com>
Thu, 1 Dec 1994 09:56:26 +0000 (09:56 +0000)
edit.c [new file with mode: 0644]
game.c [new file with mode: 0644]
usermove.c [new file with mode: 0644]

diff --git a/edit.c b/edit.c
new file mode 100644 (file)
index 0000000..346a5f7
--- /dev/null
+++ b/edit.c
@@ -0,0 +1,642 @@
+/* %W% %G% %U% - (c) Copyright 1987, 1988 Chuck Simmons */
+
+/*
+ *    Copyright (C) 1987, 1988 Chuck Simmons
+ * 
+ * See the file COPYING, distributed with empire, for restriction
+ * and warranty information.
+ */
+
+/*
+edit.c -- Routines to handle edit mode commands.
+*/
+
+#ifdef SYSV
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#include <curses.h>
+#include <ctype.h>
+#include "empire.h"
+#include "extern.h"
+
+void
+edit(edit_cursor)
+long edit_cursor;
+{
+       char e_cursor();
+       void e_leave(), e_print(), e_random();
+       void e_stasis(), e_move(), e_end(), e_wake(), e_sleep();
+       void e_info(), e_prod(), e_help(), e_explore();
+       void e_fill(), e_land(), e_city_func(), e_transport();
+       void e_attack(), e_repair();
+
+       long path_start;
+       int path_type;
+       char e;
+       
+       path_start = -1; /* not building a path yet */
+       
+       for (;;) { /* until user gives command to leave */
+               display_loc_u (edit_cursor); /* position cursor */
+               e = e_cursor (&edit_cursor); /* handle cursor movement */
+
+               switch (e) {
+               case 'B': /* change city production */
+                       e_prod (edit_cursor);
+                       break;
+               case 'F': /* fill */
+                       e_fill (edit_cursor);
+                       break;
+               case 'G': /* explore */
+                       e_explore (edit_cursor);
+                       break;
+               case 'H': /* help */
+                       e_help ();
+                       break;
+               case 'I': /* directional stasis */
+                       e_stasis (edit_cursor);
+                       break;
+               case 'K': /* wake up anything and everything */
+                       e_wake (edit_cursor);
+                       break;
+               case 'L': /* land plane */
+                       e_land (edit_cursor);
+                       break;
+               case 'M': /* start move to location */
+                       path_type = NOPIECE;
+                       e_move (&path_start, edit_cursor);
+                       break;
+               case 'N': /* end move to location */
+                       e_end (&path_start, edit_cursor, path_type);
+                       break;
+               case 'O': /* leave display mode */
+                       e_leave ();
+                       return;
+               case 'P': /* print new sector */
+                       e_print (&edit_cursor);
+                       break;
+               case 'R': /* make piece move randomly */
+                       e_random (edit_cursor);
+                       break;
+               case 'S': /* sleep */
+                       e_sleep (edit_cursor);
+                       break;
+               case 'T': /* transport army */
+                       e_transport (edit_cursor);
+                       break;
+               case 'U': /* repair ship */
+                       e_repair (edit_cursor);
+                       break;
+               case 'V': /* set city function */
+                       e_city_func (&path_start, edit_cursor, &path_type);
+                       break;
+               case 'Y': /* set army func to attack */
+                       e_attack (edit_cursor);
+                       break;
+               case '?': /* request info */
+                       e_info (edit_cursor);
+                       break;
+               case '\014': /* control-L */
+                       redraw ();
+                       break;
+               default: /* bad command? */
+                       huh ();
+                       break;
+               }
+       }
+}
+
+/*
+Get the next command.  We handle cursor movement here.
+This routine is an attempt to make cursor movement reasonably
+fast.
+*/
+
+static char dirchars[] = "WwEeDdCcXxZzAaQq";
+
+char
+e_cursor (edit_cursor)
+long *edit_cursor;
+{
+       char e;
+       char *p;
+       
+       /* set up terminal */
+       (void) crmode ();
+       (void) refresh ();
+       e = getch ();
+       topini (); /* clear any error messages */
+
+       for (;;) {
+               p = strchr (dirchars, e);
+               if (!p) break;
+
+               if (!move_cursor (edit_cursor, dir_offset[(p-dirchars) / 2]))
+                       (void) beep ();
+               
+               (void) refresh ();
+               e = getch ();
+       }
+       (void) nocrmode (); /* reset terminal */
+       if (islower (e)) e = upper (e);
+       return e;
+}
+
+/*
+Leave edit mode.
+*/
+
+void
+e_leave () {
+}
+
+/*
+Print new sector.
+*/
+
+void
+e_print (edit_cursor)
+long *edit_cursor;
+{
+        int sector;
+       
+       sector = get_range ("New Sector? ", 0, NUM_SECTORS-1);
+
+       /* position cursor at center of sector */
+       *edit_cursor = sector_loc (sector);
+       sector_change (); /* allow change of sector */
+}
+
+/*
+Set the function of a piece.
+*/
+
+void
+e_set_func (loc, func)
+long loc;
+long func;
+{
+       piece_info_t *obj;
+       obj = find_obj_at_loc (loc);
+       if (obj != NULL && obj->owner == USER) {
+               obj->func = func;
+               return;
+       }
+       huh (); /* no object here */
+}
+       
+/* Set the function of a city for some piece. */
+
+void
+e_set_city_func (cityp, type, func)
+city_info_t *cityp;
+int type;
+long func;
+{
+       cityp->func[type] = func;
+}
+
+/*
+Set a piece to move randomly.
+*/
+
+void
+e_random (loc)
+long loc;
+{
+       e_set_func (loc, RANDOM);
+}
+
+void
+e_city_random (cityp, type)
+city_info_t *cityp;
+int type;
+{
+       e_set_city_func (cityp, type, RANDOM);
+}
+
+/*
+Put a ship in fill mode.
+*/
+
+void
+e_fill (loc)
+long loc;
+{
+       if (user_map[loc].contents == 'T' || user_map[loc].contents == 'C')
+               e_set_func (loc, FILL);
+       else huh ();
+}
+
+void
+e_city_fill (cityp, type)
+city_info_t *cityp;
+int type;
+{
+       if (type == TRANSPORT || type == CARRIER)
+               e_set_city_func (cityp, type, FILL);
+       else huh ();
+}
+
+/*
+Set a piece to explore.
+*/
+
+void
+e_explore (loc)
+long loc;
+{
+       e_set_func (loc, EXPLORE);
+}
+
+void
+e_city_explore (cityp, type)
+city_info_t *cityp;
+int type;
+{
+       e_set_city_func (cityp, type, EXPLORE);
+}
+
+/*
+Set a fighter to land.
+*/
+
+void
+e_land (loc)
+long loc;
+{
+       if (user_map[loc].contents == 'F')
+               e_set_func (loc, LAND);
+       else huh ();
+}
+/*
+Set an army's function to TRANSPORT.
+*/
+
+void
+e_transport (loc)
+long loc;
+{
+       if (user_map[loc].contents == 'A')
+               e_set_func (loc, WFTRANSPORT);
+       else huh ();
+}
+
+/*
+Set an army's function to ATTACK.
+*/
+
+void
+e_attack (loc)
+long loc;
+{
+       if (user_map[loc].contents == 'A')
+               e_set_func (loc, ARMYATTACK);
+       else huh ();
+}
+
+void
+e_city_attack (cityp, type)
+city_info_t *cityp;
+int type;
+{
+       if (type == ARMY)
+               e_set_city_func (cityp, type, ARMYATTACK);
+       else huh ();
+}
+
+/*
+Set a ship's function to REPAIR.
+*/
+
+void
+e_repair (loc)
+long loc;
+{
+       if (strchr ("PDSTBC", user_map[loc].contents))
+               e_set_func (loc, REPAIR);
+       else huh ();
+}
+
+void
+e_city_repair (cityp, type)
+city_info_t *cityp;
+int type;
+{
+       if (type == ARMY || type == FIGHTER || type == SATELLITE)
+               huh ();
+       else e_set_city_func (cityp, type, REPAIR);
+}
+
+/*
+Set object to move in a direction.
+*/
+
+static char dirs[] = "WEDCXZAQ";
+void
+e_stasis (loc)
+long loc;
+{
+       char e;
+       char *p;
+       
+       if (!isupper (user_map[loc].contents)) huh (); /* no object here */
+       else if (user_map[loc].contents == 'X') huh ();
+       else {
+               e = get_chx(); /* get a direction */
+               p = strchr (dirs, e);
+
+               if (p == NULL) huh ();
+               else e_set_func (loc, (long)(MOVE_N - (p - dirs)));
+       }
+}
+
+void
+e_city_stasis (cityp, type)
+city_info_t *cityp;
+int type;
+{
+       char e;
+       char *p;
+       
+       e = get_chx(); /* get a direction */
+       p = strchr (dirs, e);
+
+       if (p == NULL) huh ();
+       else e_set_city_func (cityp, type, (long)(MOVE_N - (p - dirs)));
+}
+
+/*
+Wake up anything and everything.
+*/
+
+void
+e_wake (loc)
+long loc;
+{
+       city_info_t *cityp;
+       piece_info_t *obj;
+       int i;
+
+       cityp = find_city (loc);
+        if (cityp != NULL) {
+               for (i = 0; i < NUM_OBJECTS; i++)
+                       cityp->func[i] = NOFUNC;
+       }
+       for (obj = map[loc].objp; obj != NULL; obj = obj->loc_link.next)
+               obj->func = NOFUNC;
+}
+
+void
+e_city_wake (cityp, type)
+city_info_t *cityp;
+int type;
+{
+       e_set_city_func (cityp, type, NOFUNC);
+}
+
+/*
+Set a city's function.  We get the piece type to set, then
+the function itself.
+*/
+
+void
+e_city_func (path_start, loc, path_type)
+long *path_start;
+long loc;
+int *path_type;
+{
+       int type;
+       char e;
+       city_info_t *cityp;
+
+       cityp = find_city (loc);
+       if (!cityp || cityp->owner != USER) {
+               huh ();
+               return;
+       }
+
+       type = get_piece_name();
+       if (type == NOPIECE) {
+               huh ();
+               return;
+       }
+       
+       e = get_chx ();
+       
+       switch (e) {
+       case 'F': /* fill */
+               e_city_fill (cityp, type);
+               break;
+       case 'G': /* explore */
+               e_city_explore (cityp, type);
+               break;
+       case 'I': /* directional stasis */
+               e_city_stasis (cityp, type);
+               break;
+       case 'K': /* turn off function */
+               e_city_wake (cityp, type);
+               break;
+       case 'M': /* start move to location */
+               *path_type = type;
+               e_move (path_start, loc);
+               break;
+       case 'R': /* make piece move randomly */
+               e_city_random (cityp, type);
+               break;
+       case 'U': /* repair ship */
+               e_city_repair (cityp, type);
+               break;
+       case 'Y': /* set army func to attack */
+               e_city_attack (cityp, type);
+               break;
+       default: /* bad command? */
+               huh ();
+               break;
+       }
+}
+
+/*
+Beginning of move to location.
+*/
+
+void
+e_move (path_start, loc)
+long *path_start;
+long loc;
+{
+       if (!isupper(user_map[loc].contents)) huh (); /* nothing there? */
+       else if (user_map[loc].contents == 'X') huh (); /* enemy city? */
+        else *path_start = loc;
+}
+
+/*
+End of move to location.
+*/
+
+void
+e_end (path_start, loc, path_type)
+long *path_start;
+long loc;
+int path_type;
+{
+       city_info_t *cityp;
+       
+       if (*path_start == -1) huh (); /* no path started? */
+       else if (path_type == NOPIECE) e_set_func (*path_start, loc);
+       else {
+               cityp = find_city (*path_start);
+               ASSERT (cityp);
+               e_set_city_func (cityp, path_type, loc);
+       }
+
+       *path_start = -1; /* remember no path in progress */
+}
+
+/*
+Put a piece to sleep.
+*/
+
+void
+e_sleep (loc)
+long loc;
+{
+       if (user_map[loc].contents == 'O') huh (); /* can't sleep a city */
+       else e_set_func (loc, SENTRY);
+}
+
+/*
+Print out information about a piece.
+*/
+
+void
+e_info (edit_cursor)
+long edit_cursor;
+{
+       void e_city_info(), e_piece_info();
+
+       char ab;
+
+       ab = user_map[edit_cursor].contents;
+
+       if (ab == 'O') e_city_info (edit_cursor);
+       else if (ab == 'X' && debug) e_city_info (edit_cursor);
+       else if ((ab >= 'A') && (ab <= 'T'))
+               e_piece_info (edit_cursor, ab);
+       else if ((ab >= 'a') && (ab <= 't') && (debug))
+               e_piece_info (edit_cursor, ab);
+       else huh ();
+}
+
+/*
+Print info about a piece.
+*/
+
+void
+e_piece_info (edit_cursor, ab)
+long edit_cursor;
+char ab;
+{
+       piece_info_t *obj;
+       int type;
+       char *p;
+
+       ab = upper (ab);
+       p = strchr (type_chars, ab);
+       type = p - type_chars;
+
+       obj = find_obj (type, edit_cursor);
+       ASSERT (obj != NULL);
+       describe_obj (obj);
+}
+
+/*
+Display info on a city.
+*/
+
+void
+e_city_info (edit_cursor)
+long edit_cursor;
+{
+       piece_info_t *obj;
+       city_info_t *cityp;
+       int f, s;
+       char func_buf[STRSIZE];
+       char temp_buf[STRSIZE];
+       char junk_buf2[STRSIZE];
+
+       error (0); /* clear line */
+
+       f = 0; /* no fighters counted yet */
+       for (obj = map[edit_cursor].objp; obj != NULL;
+               obj = obj->loc_link.next)
+                       if (obj->type == FIGHTER) f++;
+
+       s = 0; /* no ships counted yet */
+       for (obj = map[edit_cursor].objp; obj != NULL;
+               obj = obj->loc_link.next)
+                       if (obj->type >= DESTROYER) s++;
+
+       if (f == 1 && s == 1) 
+               (void) sprintf (jnkbuf, "1 fighter landed, 1 ship docked");
+       else if (f == 1)
+               (void) sprintf (jnkbuf, "1 fighter landed, %d ships docked", s);
+       else if (s == 1)
+               (void) sprintf (jnkbuf, "%d fighters landed, 1 ship docked", f);
+       else (void) sprintf (jnkbuf, "%d fighters landed, %d ships docked", f, s);
+
+       cityp = find_city (edit_cursor);
+       ASSERT (cityp != NULL);
+
+       *func_buf = 0; /* nothing in buffer */
+       for (s = 0; s < NUM_OBJECTS; s++) { /* for each piece */
+               if (cityp->func[s] < 0)
+                       (void) sprintf (temp_buf, "%c:%s; ",
+                               piece_attr[s].sname,
+                               func_name[FUNCI(cityp->func[s])]);
+               else (void) sprintf (temp_buf, "%c: %d;",
+                               piece_attr[s].sname,
+                               cityp->func[s]);
+               
+               (void) strcat (func_buf, temp_buf);
+       }
+
+       (void) sprintf (junk_buf2,
+               "City at location %d will complete %s on round %d",
+               cityp->loc,
+               piece_attr[cityp->prod].article,
+               date + piece_attr[cityp->prod].build_time - cityp->work);
+
+       info (junk_buf2, jnkbuf, func_buf);
+}
+
+/*
+Change city production.
+*/
+
+void
+e_prod (loc)
+long loc;
+{
+       city_info_t *cityp;
+       
+       cityp = find_city (loc);
+
+       if (cityp == NULL) huh (); /* no city? */
+       else set_prod (cityp);
+}
+
+/*
+get help
+*/
+
+void
+e_help () {
+       help (help_edit, edit_lines);
+       prompt ("Press any key to continue: ");
+       (void) get_chx ();
+}
diff --git a/game.c b/game.c
new file mode 100644 (file)
index 0000000..68d74a7
--- /dev/null
+++ b/game.c
@@ -0,0 +1,852 @@
+/* %W% %G% %U% - (c) Copyright 1987, 1988 Chuck Simmons */
+
+/*
+ *    Copyright (C) 1987, 1988 Chuck Simmons
+ * 
+ * See the file COPYING, distributed with empire, for restriction
+ * and warranty information.
+ */
+
+/*
+game.c -- Routines to initialize, save, and restore a game.
+*/
+
+#ifdef SYSV
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+
+#include <ctype.h>
+#include <curses.h>
+#include "empire.h"
+#include "extern.h"
+
+/*
+Initialize a new game.  Here we generate a new random map, put cities
+on the map, select cities for each opponent, and zero out the lists of
+pieces on the board.
+*/
+
+void init_game () {
+       void make_map(), place_cities();
+
+       long i;
+
+       kill_display (); /* nothing on screen */
+       automove = FALSE;
+       resigned = FALSE;
+       debug = FALSE;
+       print_debug = FALSE;
+       print_vmap = FALSE;
+       trace_pmap = FALSE;
+       save_movie = FALSE;
+       win = 0;
+       date = 0; /* no date yet */
+       user_score = 0;
+       comp_score = 0;
+       
+       for (i = 0; i < MAP_SIZE; i++) {
+               user_map[i].contents = ' '; /* nothing seen yet */
+               user_map[i].seen = 0;
+               comp_map[i].contents = ' ';
+               comp_map[i].seen = 0;
+       }
+       for (i = 0; i < NUM_OBJECTS; i++) {
+               user_obj[i] = NULL;
+               comp_obj[i] = NULL;
+       }
+       free_list = NULL; /* nothing free yet */
+       for (i = 0; i < LIST_SIZE; i++) { /* for each object */
+               piece_info_t *obj = &(object[i]);
+               obj->hits = 0; /* mark object as dead */
+               obj->owner = UNOWNED;
+               LINK (free_list, obj, piece_link); 
+       }
+
+       make_map (); /* make land and water */
+
+       do {
+               for (i = 0; i < MAP_SIZE; i ++) { /* remove cities */
+                       if (map[i].contents == '*')
+                               map[i].contents = '+'; /* land */
+               }
+               place_cities (); /* place cities on map */
+       } while (!select_cities ()); /* choose a city for each player */
+}
+
+/*
+Create a map.  To do this, we first randomly assign heights to each
+map location.  Then we smooth these heights.  The more we smooth,
+the better the land and water will clump together.  Then we decide
+how high the land will be.  We attempt to choose enough land to meet
+some required percentage.
+
+There are two parameters to this algorithm:  the amount we will smooth,
+and the ratio of land to water.  The user can provide these numbers
+at program start up.
+*/
+
+#define MAX_HEIGHT 999 /* highest height */
+
+/* these arrays give some compilers problems when they are automatic */
+static int height[2][MAP_SIZE];
+static int height_count[MAX_HEIGHT+1];
+
+void make_map () {
+       int from, to, k;
+       long i, j, sum, loc;
+
+       for (i = 0; i < MAP_SIZE; i++) /* fill map with random sand */
+               height[0][i] = irand (MAX_HEIGHT);
+
+       from = 0;
+       to = 1;
+       for (i = 0; i < SMOOTH; i++) { /* smooth the map */
+           for (j = 0; j < MAP_SIZE; j++) {
+               sum = height[from][j];
+               for (k = 0; k < 8; k++) {
+                       loc = j + dir_offset[k];
+                       /* edges get smoothed in a wierd fashion */
+                       if (loc < 0 || loc >= MAP_SIZE) loc = j;
+                       sum += height[from][loc];
+               }
+               height[to][j] = sum / 9;
+           }
+           k = to; /* swap to and from */
+           to = from;
+           from = k;
+       }
+
+       /* count the number of cells at each height */
+       for (i = 0; i <= MAX_HEIGHT; i++)
+               height_count[i] = 0;
+
+       for (i = 0; i <= MAP_SIZE; i++)
+               height_count[height[from][i]]++;
+
+       /* find the water line */
+       loc = MAX_HEIGHT; /* default to all water */
+       sum = 0;
+       for (i = 0; i <= MAX_HEIGHT; i++) {
+               sum += height_count[i];
+               if (sum * 100 / MAP_SIZE > WATER_RATIO && sum >= NUM_CITY) {
+                       loc = i; /* this is last height that is water */
+                       break;
+               }
+       }
+
+       /* mark the land and water */
+       for (i = 0; i < MAP_SIZE; i ++) {
+               if (height[from][i] > loc)
+                       map[i].contents = '+'; /* land */
+               else map[i].contents = '.'; /* water */
+
+               map[i].objp = NULL; /* nothing in cell yet */
+               map[i].cityp = NULL;
+
+               j = loc_col (i);
+               k = loc_row (i);
+
+               map[i].on_board = !(j == 0 || j == MAP_WIDTH-1 
+                                || k == 0 || k == MAP_HEIGHT-1);
+       }
+}
+
+/*
+Randomly place cities on the land.  There is a minimum distance that
+should exist between cities.  We maintain a list of acceptable land cells
+on which a city may be placed.  We randomly choose elements from this
+list until all the cities are placed.  After each choice of a land cell
+for a city, we remove land cells which are too close to the city.
+*/
+
+/* avoid compiler problems with large automatic arrays */
+static long land[MAP_SIZE];
+
+void place_cities () {
+       long regen_land(), remove_land();
+
+       long placed, i, loc;
+       long num_land;
+
+       num_land = 0; /* nothing in land array yet */
+       placed = 0; /* nothing placed yet */
+       while (placed < NUM_CITY) {
+               while (num_land == 0) num_land = regen_land (placed);
+               i = irand (num_land-1); /* select random piece of land */
+               loc = land[i];
+               
+               city[placed].loc = loc;
+               city[placed].owner = UNOWNED;
+               city[placed].work = 0;
+               city[placed].prod = NOPIECE;
+               
+               for (i = 0; i < NUM_OBJECTS; i++)
+                       city[placed].func[i] = NOFUNC; /* no function */
+                       
+               map[loc].contents = '*';
+               map[loc].cityp = &(city[placed]);
+               placed++;
+
+               /* Now remove any land too close to selected land. */
+               num_land = remove_land (loc, num_land);
+       }
+}
+
+/*
+When we run out of available land, we recreate our land list.  We
+put all land in the list, decrement the min_city_dist, and then
+remove any land which is too close to a city.
+*/
+
+long regen_land (placed)
+long placed;
+{
+       long num_land;
+       long i;
+
+       num_land = 0;
+       for (i = 0; i < MAP_SIZE; i++) {
+               if (map[i].on_board && map[i].contents == '+') {
+                       land[num_land] = i; /* remember piece of land */
+                       num_land++; /* remember number of pieces */
+               }
+       }
+       if (placed > 0) { /* don't decrement 1st time */
+               MIN_CITY_DIST -= 1;
+               ASSERT (MIN_CITY_DIST >= 0);
+       }
+       for (i = 0; i < placed; i++) { /* for each placed city */
+               num_land = remove_land (city[i].loc, num_land);
+       }
+       return (num_land);
+}
+
+/*
+Remove land that is too close to a city.
+*/
+
+long remove_land (loc, num_land)
+long loc, num_land;
+{
+       long new, i;
+
+       new = 0; /* nothing kept yet */
+       for (i = 0; i < num_land; i++) {
+               if (dist (loc, land[i]) >= MIN_CITY_DIST) {
+                       land[new] = land[i];
+                       new++;
+               }
+       }
+       return (new);
+}
+
+/*
+Here we select the cities for the user and the computer.  Our choice of
+cities will be heavily dependent on the difficulty level the user desires.
+
+Our algorithm will not guarantee that either player will eventually be
+able to move armies to any continent on the map.  There may be continents
+which are unreachable by sea.  Consider the case of an island in a lake.
+If the lake has no shore cities, then there is no way for a boat to reach
+the island.  Our hope is that there will be enough water on the map, or enough
+land, and that there will be enough cities, to make this case extremely rare.
+
+First we make a list of continents which contain at least two cities, one
+or more of which is on the coast.  If there are no such continents, we return
+FALSE, and our caller should decide again where cities should be placed
+on the map.  While making this list, we will rank the continents.  Our ranking
+is based on the thought that shore cities are better than inland cities,
+that any city is very important, and that the land area of a continent
+is mildly important.  Usually, we expect every continent to have a different
+ranking.  It will be unusual to have two continents with the same land area,
+the same number of shore cities, and the same number of inland cities.  When
+this is not the case, the first city encountered will be given the highest
+rank.
+
+We then rank pairs of continents.  We tell the user the number of different
+ranks, and ask the user what rank they want to use.  This is how the
+user specifies the difficulty level.  Using that pair, we have now decided
+on a continent for each player.  We now choose a random city on each continent,
+making sure the cities are not the same.
+*/
+
+#define MAX_CONT 10 /* most continents we will allow */
+
+typedef struct cont { /* a continent */
+       long value; /* value of continent */
+       int ncity; /* number of cities */
+       city_info_t * cityp[NUM_CITY]; /* pointer to city */
+} cont_t;
+
+typedef struct pair {
+       long value; /* value of pair for user */
+       int user_cont; /* index to user continent */
+       int comp_cont; /* index to computer continent */
+} pair_t;
+
+static int marked[MAP_SIZE]; /* list of examine cells */
+static int ncont; /* number of continents */
+static cont_t cont_tab[MAX_CONT]; /* list of good continenets */
+static int rank_tab[MAX_CONT]; /* indices to cont_tab in order of rank */
+static pair_t pair_tab[MAX_CONT*MAX_CONT]; /* ranked pairs of continents */
+
+int select_cities () {
+       void find_cont(), make_pair();
+
+       long compi, useri;
+       city_info_t *compp, *userp;
+       int comp_cont, user_cont;
+       int pair;
+
+       find_cont (); /* find and rank the continents */
+       if (ncont == 0) return (FALSE); /* there are no good continents */
+
+       make_pair (); /* create list of ranked pairs */
+
+       (void) sprintf (jnkbuf,
+               "Choose a difficulty level where 0 is easy and %d is hard: ",
+               ncont*ncont-1);
+
+       pair = get_range (jnkbuf, 0, ncont*ncont-1);
+       comp_cont = pair_tab[pair].comp_cont;
+       user_cont = pair_tab[pair].user_cont;
+
+       compi = irand ((long)cont_tab[comp_cont].ncity);
+       compp = cont_tab[comp_cont].cityp[compi];
+
+       do { /* select different user city */
+               useri = irand ((long)cont_tab[user_cont].ncity);
+               userp = cont_tab[user_cont].cityp[useri];
+       } while (userp == compp);
+
+       addprintf ("Your city is at %d.", userp->loc);
+       delay (); /* let user see output before we set_prod */
+
+       /* update city and map */
+       compp->owner = COMP;
+       compp->prod = ARMY;
+       compp->work = 0;
+       scan (comp_map, compp->loc);
+
+       userp->owner = USER;
+       userp->work = 0;
+       scan (user_map, userp->loc);
+       set_prod (userp);
+       return (TRUE);
+}
+
+/*
+Find all continents with 2 cities or more, one of which must be a shore
+city.  We rank the continents.
+*/
+
+void find_cont () {
+       long i;
+       long mapi;
+
+       for (i = 0; i < MAP_SIZE; i++) marked[i] = 0; /* nothing marked yet */
+
+       ncont = 0; /* no continents found yet */
+       mapi = 0;
+
+       while (ncont < MAX_CONT)
+               if (!find_next (&mapi)) return; /* all found */
+}
+
+/*
+Find the next continent and insert it in the rank table.
+If there are no more continents, we return false.
+*/
+
+int find_next (mapi)
+long *mapi;
+{
+       long i, val;
+
+       for (;;) {
+               if (*mapi >= MAP_SIZE) return (FALSE);
+
+               if (!map[*mapi].on_board || marked[*mapi]
+                       || map[*mapi].contents == '.') *mapi += 1;
+               else if (good_cont (*mapi)) {
+                       rank_tab[ncont] = ncont; /* insert cont in rank tab */
+                       val = cont_tab[ncont].value;
+
+                       for (i = ncont; i > 0; i--) { /* bubble up new rank */
+                               if (val > cont_tab[rank_tab[i-1]].value) {
+                                       rank_tab[i] = rank_tab[i-1];
+                                       rank_tab[i-1] = ncont;
+                               }
+                               else break;
+                       }
+                       ncont++; /* count continents */
+                       return (TRUE);
+               }
+       }
+}
+
+/*
+Map out the current continent.  We mark every piece of land on the continent,
+count the cities, shore cities, and land area of the continent.  If the
+continent contains 2 cities and a shore city, we set the value of the
+continent and return true.  Otherwise we return false.
+*/
+
+static long ncity, nland, nshore;
+
+int good_cont (mapi)
+long mapi;
+{
+       static void mark_cont();
+
+       long val;
+
+       ncity = 0; /* nothing seen yet */
+       nland = 0;
+       nshore = 0;
+
+       mark_cont (mapi);
+
+       if (nshore < 1 || ncity < 2) return (FALSE);
+
+       /* The first two cities, one of which must be a shore city,
+       don't contribute to the value.  Otherwise shore cities are
+       worth 3/2 an inland city.  A city is worth 1000 times as much
+       as land area. */
+
+       if (ncity == nshore) val = (nshore - 2) * 3;
+       else val = (nshore-1) * 3 + (ncity - nshore - 1) * 2;
+
+       val *= 1000; /* cities are worth a lot */
+       val += nland;
+       cont_tab[ncont].value = val;
+       cont_tab[ncont].ncity = ncity;
+       return (TRUE);
+}
+
+/*
+Mark a continent.  This recursive algorithm marks the current square
+and counts it if it is land or city.  If it is city, we also check
+to see if it is a shore city, and we install it in the list of
+cities for the continent.  We then examine each surrounding cell.
+*/
+
+static void
+mark_cont (mapi)
+long mapi;
+{
+       int i;
+
+       if (marked[mapi] || map[mapi].contents == '.'
+               || !map[mapi].on_board) return;
+
+       marked[mapi] = 1; /* mark this cell seen */
+       nland++; /* count land on continent */
+
+       if (map[mapi].contents == '*') { /* a city? */
+               cont_tab[ncont].cityp[ncity] = map[mapi].cityp;
+               ncity++;
+               if (rmap_shore (mapi)) nshore++;
+       }
+
+       for (i = 0; i < 8; i++) /* look at surrounding squares */
+               mark_cont (mapi + dir_offset[i]);
+}
+
+/*
+Create a list of pairs of continents in a ranked order.  The first
+element in the list is the pair which is easiest for the user to
+win with.  Our ranking is simply based on the difference in value
+between the user's continent and the computer's continent.
+*/
+
+void make_pair () {
+       int i, j, k, npair;
+       long val;
+
+       npair = 0; /* none yet */
+
+       for (i = 0; i < ncont; i++)
+       for (j = 0; j < ncont; j++) { /* loop through all continents */
+               val = cont_tab[i].value - cont_tab[j].value;
+               pair_tab[npair].value = val;
+               pair_tab[npair].user_cont = i;
+               pair_tab[npair].comp_cont = j;
+
+               for (k = npair; k > 0; k--) { /* bubble up new rank */
+                       if (val > pair_tab[k-1].value) {
+                               pair_tab[k] = pair_tab[k-1];
+                               pair_tab[k-1].user_cont = i;
+                               pair_tab[k-1].comp_cont = j;
+                       }
+                       else break;
+               }
+               npair++; /* count pairs */
+       }
+}
+
+/*
+Save a game.  We save the game in emp_save.dat.  Someday we may want
+to ask the user for a file name.  If we cannot save the game, we will
+tell the user why.
+*/
+
+/* macro to save typing; write an array, return if it fails */
+#define wbuf(buf) if (!xwrite (f, (char *)buf, sizeof (buf))) return
+#define wval(val) if (!xwrite (f, (char *)&val, sizeof (val))) return
+
+void save_game () {
+       FILE *f; /* file to save game in */
+
+       f = fopen ("empsave.dat", "w"); /* open for output */
+       if (f == NULL) {
+               perror ("Cannot save empsave.dat");
+               return;
+       }
+       wbuf (map);
+       wbuf (comp_map);
+       wbuf (user_map);
+       wbuf (city);
+       wbuf (object);
+       wbuf (user_obj);
+       wbuf (comp_obj);
+       wval (free_list);
+       wval (date);
+       wval (automove);
+       wval (resigned);
+       wval (debug);
+       wval (win);
+       wval (save_movie);
+       wval (user_score);
+       wval (comp_score);
+
+       (void) fclose (f);
+       topmsg (3, "Game saved.");
+}
+
+/*
+Recover a saved game from emp_save.dat.
+We return TRUE if we succeed, otherwise FALSE.
+*/
+
+#define rbuf(buf) if (!xread (f, (char *)buf, sizeof(buf))) return (FALSE);
+#define rval(val) if (!xread (f, (char *)&val, sizeof(val))) return (FALSE);
+
+int restore_game () {
+       void read_embark();
+       
+       FILE *f; /* file to save game in */
+       long i;
+       piece_info_t **list;
+       piece_info_t *obj;
+
+       f = fopen ("empsave.dat", "r"); /* open for input */
+       if (f == NULL) {
+               perror ("Cannot open empsave.dat");
+               return (FALSE);
+       }
+       rbuf (map);
+       rbuf (comp_map);
+       rbuf (user_map);
+       rbuf (city);
+       rbuf (object);
+       rbuf (user_obj);
+       rbuf (comp_obj);
+       rval (free_list);
+       rval (date);
+       rval (automove);
+       rval (resigned);
+       rval (debug);
+       rval (win);
+       rval (save_movie);
+       rval (user_score);
+       rval (comp_score);
+
+       /* Our pointers may not be valid because of source
+       changes or other things.  We recreate them. */
+       
+       free_list = NULL; /* zero all ptrs */
+       for (i = 0; i < MAP_SIZE; i++) {
+               map[i].cityp = NULL;
+               map[i].objp = NULL;
+       }
+       for (i = 0; i < LIST_SIZE; i++) {
+               object[i].loc_link.next = NULL;
+               object[i].loc_link.prev = NULL;
+               object[i].cargo_link.next = NULL;
+               object[i].cargo_link.prev = NULL;
+               object[i].piece_link.next = NULL;
+               object[i].piece_link.prev = NULL;
+               object[i].ship = NULL;
+               object[i].cargo = NULL;
+       }
+       for (i = 0; i < NUM_OBJECTS; i++) {
+               comp_obj[i] = NULL;
+               user_obj[i] = NULL;
+       }
+       /* put cities on map */
+       for (i = 0; i < NUM_CITY; i++)
+               map[city[i].loc].cityp = &(city[i]);
+       
+       /* put pieces in free list or on map and in object lists */
+       for (i = 0; i < LIST_SIZE; i++) {
+               obj = &(object[i]);
+               if (object[i].owner == UNOWNED || object[i].hits == 0) {
+                       LINK (free_list, obj, piece_link);
+               }
+               else {
+                       list = LIST (object[i].owner);
+                       LINK (list[object[i].type], obj, piece_link);
+                       LINK (map[object[i].loc].objp, obj, loc_link);
+               }
+       }
+       
+       /* Embark armies and fighters. */
+       read_embark (user_obj[TRANSPORT], ARMY);
+       read_embark (user_obj[CARRIER], FIGHTER);
+       read_embark (comp_obj[TRANSPORT], ARMY);
+       read_embark (comp_obj[CARRIER], FIGHTER);
+       
+       (void) fclose (f);
+       kill_display (); /* what we had is no longer good */
+       topmsg (3, "Game restored from empsave.dat.");
+       return (TRUE);
+}
+       
+/*
+Embark cargo on a ship.  We loop through the list of ships.
+We then loop through the pieces at the ship's location until
+the ship has the same amount of cargo it previously had.
+*/
+
+void read_embark (list, piece_type)
+piece_info_t *list;
+int piece_type;
+{
+       void inconsistent();
+
+       piece_info_t *ship;
+       piece_info_t *obj;
+       int count;
+
+       for (ship = list; ship != NULL; ship = ship->piece_link.next) {
+               count = ship->count; /* get # of pieces we need */
+               if (count < 0) inconsistent ();
+               ship->count = 0; /* nothing on board yet */
+               for (obj = map[ship->loc].objp; obj && count;
+                   obj = obj->loc_link.next) {
+                       if (obj->ship == NULL && obj->type == piece_type) {
+                               embark (ship, obj);
+                               count -= 1;
+                       }
+               }
+               if (count) inconsistent ();
+       }
+}
+
+void inconsistent () {
+       (void) printf ("empsave.dat is inconsistent.  Please remove it.\n");
+       exit (1);
+}
+
+/*
+Write a buffer to a file.  If we cannot write everything, return FALSE.
+Also, tell the user why the write did not work if it didn't.
+*/
+
+int xwrite (f, buf, size)
+FILE *f;
+char *buf;
+int size;
+{
+       int bytes;
+       bytes = fwrite (buf, 1, size, f);
+       if (bytes == -1) {
+               perror ("Write to save file failed");
+               return (FALSE);
+       }
+       if (bytes != size) {
+               perror ("Cannot complete write to save file.\n");
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+/*
+Read a buffer from a file.  If the read fails, we tell the user why
+and return FALSE.
+*/
+
+int xread (f, buf, size)
+FILE *f;
+char *buf;
+int size;
+{
+       int bytes;
+
+       bytes = fread (buf, 1, size, f);
+       if (bytes == -1) {
+               perror ("Read from save file failed");
+               return (FALSE);
+       }
+       if (bytes != size) {
+               perror ("Saved file is too short.\n");
+               return (FALSE);
+       }
+       return (TRUE);
+}
+
+/*
+Save a movie screen.  For each cell on the board, we write out
+the character that would appear on either the user's or the
+computer's screen.  This information is appended to 'empmovie.dat'.
+*/
+
+extern char city_char[];
+static char mapbuf[MAP_SIZE];
+
+void
+save_movie_screen ()
+{
+       FILE *f; /* file to save game in */
+       long i;
+       piece_info_t *p;
+
+       f = fopen ("empmovie.dat", "a"); /* open for append */
+       if (f == NULL) {
+               perror ("Cannot open empmovie.dat");
+               return;
+       }
+
+       for (i = 0; i < MAP_SIZE; i++) {
+               if (map[i].cityp) mapbuf[i] = city_char[map[i].cityp->owner];
+               else {
+                       p = find_obj_at_loc (i);
+                       
+                       if (!p) mapbuf[i] = map[i].contents;
+                       else if (p->owner == USER)
+                               mapbuf[i] = piece_attr[p->type].sname;
+                       else mapbuf[i] = tolower (piece_attr[p->type].sname);
+               }
+       }
+       wbuf (mapbuf);
+       (void) fclose (f);
+}
+
+/*
+Replay a movie.  We read each buffer from the file and
+print it using a zoomed display.
+*/
+
+void
+replay_movie ()
+{
+       void print_movie_cell();
+
+       FILE *f; /* file to save game in */
+       int row_inc, col_inc;
+       int r, c;
+       int round;
+
+       
+       f = fopen ("empmovie.dat", "r"); /* open for input */
+       if (f == NULL) {
+               perror ("Cannot open empmovie.dat");
+               return;
+       }
+       round = 0;
+       clear_screen ();
+       for (;;) {
+               if (fread ((char *)mapbuf, 1, sizeof (mapbuf), f) != sizeof (mapbuf)) break;
+               round += 1;
+               
+               stat_display (mapbuf, round);
+               
+               row_inc = (MAP_HEIGHT + lines - NUMTOPS - 1) / (lines - NUMTOPS);
+               col_inc = (MAP_WIDTH + cols - 1) / (cols - 1);
+       
+               for (r = 0; r < MAP_HEIGHT; r += row_inc)
+               for (c = 0; c < MAP_WIDTH; c += col_inc)
+               print_movie_cell (mapbuf, r, c, row_inc, col_inc);
+               
+               (void) refresh ();
+               delay ();
+       }
+       (void) fclose (f);
+}
+
+/*
+Display statistics about the game.  At the top of the screen we
+print:
+
+nn O  nn A  nn F  nn P  nn D  nn S  nn T  nn C  nn B  nn Z  xxxxx
+nn X  nn a  nn f  nn p  nn d  nn s  nn t  nn c  nn b  nn z  xxxxx
+
+There may be objects in cities and boats that aren't displayed.
+The "xxxxx" field is the cumulative cost of building the hardware.
+*/
+
+/* in declared order, with city first */
+static char *pieces = "OAFPDSTCBZXafpdstcbz";
+
+stat_display (mbuf, round)
+char *mbuf;
+int round;
+{
+       long i;
+       int counts[2*NUM_OBJECTS+2];
+       int user_cost, comp_cost;
+       char *p;
+       
+       (void) bzero ((char *)counts, sizeof (counts));
+       
+       for (i = 0; i < MAP_SIZE; i++) {
+               p = strchr (pieces, mbuf[i]);
+               if (p) counts[p-pieces] += 1;
+       }
+       user_cost = 0;
+       for (i = 1; i <= NUM_OBJECTS; i++)
+               user_cost += counts[i] * piece_attr[i-1].build_time;
+               
+       comp_cost = 0;
+       for (i = NUM_OBJECTS+2; i <= 2*NUM_OBJECTS+1; i++)
+               comp_cost += counts[i] * piece_attr[i-NUM_OBJECTS-2].build_time;
+               
+       for (i = 0; i < NUM_OBJECTS+1; i++) {
+               pos_str (1, (int) i * 6, "%2d %c  ", counts[i], pieces[i]);
+               pos_str (2,(int) i * 6, "%2d %c  ", counts[i+NUM_OBJECTS+1], pieces[i+NUM_OBJECTS+1]);
+       }
+
+       pos_str (1, (int) i * 6, "%5d", user_cost);
+       pos_str (2, (int) i * 6, "%5d", comp_cost);
+       pos_str (0, 0, "Round %3d", (round + 1) / 2);
+}
+
+/*
+Print a single cell in condensed format.
+*/
+
+extern char zoom_list[];
+
+void
+print_movie_cell (mbuf, row, col, row_inc, col_inc)
+char *mbuf;
+int row, col;
+int row_inc, col_inc;
+{
+       int r, c;
+       char cell;
+
+       cell = ' ';
+       for (r = row; r < row + row_inc; r++)
+       for (c = col; c < col + col_inc; c++)
+       if (strchr (zoom_list, mbuf[row_col_loc(r,c)])
+               < strchr (zoom_list, cell))
+       cell = mbuf[row_col_loc(r,c)];
+       
+       (void) move (row/row_inc + NUMTOPS, col/col_inc);
+       (void) addch ((chtype)cell);
+}
diff --git a/usermove.c b/usermove.c
new file mode 100644 (file)
index 0000000..865251d
--- /dev/null
@@ -0,0 +1,1108 @@
+/* %W% %G% %U% - (c) Copyright 1987, 1988 Chuck Simmons */
+
+/*
+ *    Copyright (C) 1987, 1988 Chuck Simmons
+ * 
+ * See the file COPYING, distributed with empire, for restriction
+ * and warranty information.
+ */
+
+/*
+usermove.c -- Let the user move her troops.
+*/
+
+#include <curses.h>
+#include <ctype.h>
+#include "empire.h"
+#include "extern.h"
+
+void
+user_move () {
+       void piece_move();
+
+       int i, j, sec, sec_start;
+       piece_info_t *obj, *next_obj;
+       int prod;
+
+       /* First we loop through objects to update the user's view
+       of the world and perform any other necessary processing.
+       We would like to have the world view up to date before
+       asking the user any questions.  This means that we should
+       also scan through all cities before possibly asking the
+       user what to produce in each city. */
+
+       for (i = 0; i < NUM_OBJECTS; i++)
+       for (obj = user_obj[i]; obj != NULL; obj = obj->piece_link.next) {
+               obj->moved = 0; /* nothing moved yet */
+               scan (user_map, obj->loc); /* refresh user's view of world */
+       }
+
+       /* produce new hardware */
+       for (i = 0; i < NUM_CITY; i++)
+           if (city[i].owner == USER) {
+               scan (user_map, city[i].loc);
+               prod = city[i].prod;
+
+               if (prod == NOPIECE) { /* need production? */
+                       set_prod (&(city[i])); /* ask user what to produce */
+               }
+               else if (city[i].work++ >= (long)piece_attr[prod].build_time) {
+                       comment ("City at %d has completed %s.",
+                               city[i].loc, piece_attr[prod].article);
+
+                       produce (&city[i]);
+                       /* produce should set object.moved to 0 */
+               }
+       }
+
+       /* move all satellites */
+       for (obj = user_obj[SATELLITE]; obj != NULL; obj = next_obj) {
+               next_obj = obj->piece_link.next;
+               move_sat (obj);
+       }
+       
+       sec_start = cur_sector (); /* get currently displayed sector */
+       if (sec_start == -1) sec_start = 0;
+
+       /* loop through sectors, moving every piece in the sector */
+       for (i = sec_start; i < sec_start + NUM_SECTORS; i++) {
+               sec = i % NUM_SECTORS;
+               sector_change (); /* allow screen to be redrawn */
+
+               for (j = 0; j < NUM_OBJECTS; j++) /* loop through obj lists */
+               for (obj = user_obj[move_order[j]]; obj != NULL;
+                       obj = next_obj) { /* loop through objs in list */
+                       next_obj = obj->piece_link.next;
+
+                       if (!obj->moved) /* object not moved yet? */
+                       if (loc_sector (obj->loc) == sec) /* object in sector? */
+                       piece_move (obj); /* yup; move the object */
+               }
+               if (cur_sector () == sec) { /* is sector displayed? */
+                       print_sector_u (sec); /* make screen up-to-date */
+                       (void) refresh (); /* show it to the user */
+               }
+       }
+       if (save_movie) save_movie_screen ();
+}
+
+/*
+Move a piece.  We loop until all the moves of a piece are made.  Within
+the loop, we first awaken the piece if it is adjacent to an enemy piece.
+Then we attempt to handle any preprogrammed function for the piece.  If
+the piece has not moved after this, we ask the user what to do.
+*/
+
+void
+piece_move (obj)
+piece_info_t *obj;
+{
+       void move_random(), move_fill(), move_land(), move_explore();
+       void move_path(), move_dir(), move_armyload(), ask_user();
+       void move_armyattack(), move_ttload(), move_repair();
+       void move_transport();
+
+       int changed_loc;
+       int speed, max_hits;
+       int saved_moves;
+       int need_input;
+       long saved_loc;
+       city_info_t *cityp;
+
+       /* set func for piece if on city */
+       cityp = find_city (obj->loc);
+       if (cityp != NULL)
+               if (cityp->func[obj->type] != NOFUNC)
+                       obj->func = cityp->func[obj->type];
+
+       changed_loc = FALSE; /* not changed yet */
+       speed = piece_attr[obj->type].speed;
+       max_hits = piece_attr[obj->type].max_hits;
+       need_input = FALSE; /* don't require user input yet */
+
+       while (obj->moved < obj_moves (obj)) {
+               saved_moves = obj->moved; /* save moves made */
+               saved_loc = obj->loc; /* remember starting location */
+
+               if (awake (obj) || need_input){ /* need user input? */
+                       ask_user (obj);
+                       topini (); /* clear info lines */
+                       display_loc_u (obj->loc); /* let user see result */
+                       (void) refresh ();
+                       need_input = FALSE; /* we got it */
+               }
+               
+               if (obj->moved == saved_moves) /* user set function? */
+               switch (obj->func) { /* handle preprogrammed function */
+               case NOFUNC:    break;
+               case RANDOM:    move_random (obj); break;
+               case SENTRY:    obj->moved = speed; break;
+               case FILL:      move_fill (obj); break;
+               case LAND:      move_land (obj); break;
+               case EXPLORE:   move_explore (obj); break;
+               case ARMYLOAD:  move_armyload (obj); break;
+               case ARMYATTACK:move_armyattack (obj); break;
+               case TTLOAD:    move_ttload (obj); break;
+               case REPAIR:    move_repair (obj); break;
+               case WFTRANSPORT: move_transport (obj); break;
+
+               case MOVE_N:
+               case MOVE_NE:
+               case MOVE_E:
+               case MOVE_SE:
+               case MOVE_S:
+               case MOVE_SW:
+               case MOVE_W:
+               case MOVE_NW:
+                       move_dir (obj); break;
+
+               default: move_path (obj); break;
+               }
+
+               if (obj->moved == saved_moves) need_input = TRUE;
+               
+               /* handle fighters specially.  If in a city or carrier, turn
+               is over and reset range to max.  Otherwise, if
+               range = 0, fighter crashes and burns and turn is over. */
+
+               if (obj->type == FIGHTER && obj->hits > 0) {
+                       if ((user_map[obj->loc].contents == 'O'
+                         || user_map[obj->loc].contents == 'C')
+                       && obj->moved > 0) {
+                               obj->range = piece_attr[FIGHTER].range;
+                               obj->moved = speed;
+                               obj->func = NOFUNC;
+                               comment ("Landing confirmed.");
+                       }
+                       else if (obj->range == 0) {
+                               comment ("Fighter at %d crashed and burned.",
+                                       obj->loc);
+                               kill_obj (obj, obj->loc);
+                       }
+               }
+
+               if (saved_loc != obj->loc) changed_loc = TRUE;
+       }
+       /* if a boat is in port, damaged, and never moved, fix some damage */
+       if (obj->hits > 0 /* still alive? */
+               && !changed_loc /* object never changed location? */
+               && obj->type != ARMY && obj->type != FIGHTER /* it is a boat? */
+               && obj->hits < max_hits /* it is damaged? */
+               && user_map[obj->loc].contents == 'O') /* it is in port? */
+       obj->hits++; /* fix some damage */
+}
+
+/*
+Move a piece at random.  We create a list of empty squares to which
+the piece can move.  If there are none, we do nothing, otherwise we 
+move the piece to a random adjacent square.
+*/
+
+void move_random (obj)
+piece_info_t *obj;
+{
+       long loc_list[8];
+       int i, nloc;
+       long loc;
+
+       nloc = 0;
+
+       for (i = 0; i < 8; i++) {
+               loc = obj->loc + dir_offset[i];
+               if (good_loc (obj, loc)) {
+                       loc_list[nloc] = loc; /* remember this location */
+                       nloc++; /* count locations we can move to */
+               }
+       }
+       if (nloc == 0) return; /* no legal move */
+       i = irand ((long)nloc-1); /* choose random direction */
+       move_obj (obj, loc_list[i]); /* move the piece */
+}
+
+/*
+Have a piece explore.  We look for the nearest unexplored territory
+which the piece can reach and have to piece move toward the
+territory.
+*/
+
+void move_explore (obj)
+piece_info_t *obj;
+{
+       path_map_t path_map[MAP_SIZE];
+       long loc;
+       char *terrain;
+
+       switch (obj->type) {
+       case ARMY:
+               loc = vmap_find_lobj (path_map, user_map, obj->loc, &user_army);
+               terrain = "+";
+               break;
+       case FIGHTER:
+               loc = vmap_find_aobj (path_map, user_map, obj->loc, &user_fighter);
+               terrain = "+.O";
+               break;
+       default:
+               loc = vmap_find_wobj (path_map, user_map, obj->loc, &user_ship);
+               terrain = ".O";
+               break;
+       }
+       
+       if (loc == obj->loc) return; /* nothing to explore */
+
+       if (user_map[loc].contents == ' ' && path_map[loc].cost == 2)
+               vmap_mark_adjacent (path_map, obj->loc);
+       else vmap_mark_path (path_map, user_map, loc);
+
+       loc = vmap_find_dir (path_map, user_map, obj->loc, terrain, " ");
+       if (loc != obj->loc) move_obj (obj, loc);
+}
+
+/*
+Move an army onto a transport when it arrives.  We scan around the
+army to find a non-full transport.  If one is present, we move the
+army to the transport and waken the army.
+*/
+
+void
+move_transport (obj)
+piece_info_t *obj;
+{
+       long loc;
+
+       /* look for an adjacent transport */
+       loc = find_transport (USER, obj->loc);
+       
+       if (loc != obj->loc) {
+               move_obj (obj, loc);
+               obj->func = NOFUNC;
+       }
+       else obj->moved = piece_attr[obj->type].speed;
+}
+
+/*
+Move an army toward the nearest loading transport.
+If there is an adjacent transport, move the army onto
+the transport, and awaken the army.
+*/
+
+static view_map_t amap[MAP_SIZE];
+
+void
+move_armyload (obj)
+piece_info_t *obj;
+{
+       long loc;
+       piece_info_t *p;
+       int i;
+
+       ABORT;
+       
+       /* look for an adjacent transport */
+       loc = find_transport (USER, obj->loc);
+
+       if (loc != obj->loc) {
+               move_obj (obj, loc);
+               obj->func = NOFUNC;
+       }
+       else { /* look for nearest non-full transport */
+               (void) memcpy (amap, user_map, sizeof (view_map_t) * MAP_SIZE);
+
+               /* mark loading transports or cities building transports */
+               for (p = user_obj[TRANSPORT]; p; p = p->piece_link.next)
+               if (p->count < obj_capacity (p)) /* not full? */
+               amap[p->loc].contents = '$';
+               
+               for (i = 0; i < NUM_CITY; i++)
+               if (city[i].owner == USER && city[i].prod == TRANSPORT)
+               amap[city[i].loc].contents = '$';
+       }
+}
+               
+/*
+Move an army toward an attackable city or enemy army.
+*/
+
+void
+move_armyattack (obj)
+piece_info_t *obj;
+{
+       path_map_t path_map[MAP_SIZE];
+       long loc;
+
+       ASSERT (obj->type == ARMY);
+
+       loc = vmap_find_lobj (path_map, user_map, obj->loc, &user_army_attack);
+       
+       if (loc == obj->loc) return; /* nothing to attack */
+
+       vmap_mark_path (path_map, user_map, loc);
+
+       loc = vmap_find_dir (path_map, user_map, obj->loc, "+", "X*a");
+       if (loc != obj->loc) move_obj (obj, loc);
+}
+
+void
+move_ttload (obj)
+piece_info_t *obj;
+{
+       ABORT;
+       obj = obj;
+}
+
+/*
+Move a ship toward port.  If the ship is healthy, wake it up.
+*/
+
+void
+move_repair (obj)
+piece_info_t *obj;
+{
+       path_map_t path_map[MAP_SIZE];
+       long loc;
+
+       ASSERT (obj->type > FIGHTER);
+       
+       if (obj->hits == piece_attr[obj->type].max_hits) {
+               obj->func = NOFUNC;
+               return;
+       }
+       
+       if (user_map[obj->loc].contents == 'O') { /* it is in port? */
+               obj->moved += 1;
+               return;
+       }
+
+       loc = vmap_find_wobj (path_map, user_map, obj->loc, &user_ship_repair);
+       
+       if (loc == obj->loc) return; /* no reachable city */
+
+       vmap_mark_path (path_map, user_map, loc);
+
+       /* try to be next to ocean to avoid enemy pieces */
+       loc = vmap_find_dir (path_map, user_map, obj->loc, ".O", ".");
+       if (loc != obj->loc) move_obj (obj, loc);
+}
+
+/*
+Here we have a transport or carrier waiting to be filled.  If the
+object is not full, we set the move count to its maximum value.
+Otherwise we awaken the object.
+*/
+
+void move_fill (obj)
+piece_info_t *obj;
+{
+       if (obj->count == obj_capacity (obj)) /* full? */
+               obj->func = NOFUNC; /* awaken full boat */
+       else obj->moved = piece_attr[obj->type].speed;
+}
+
+/*
+Here we have a piece that wants to land at the nearest carrier or
+owned city.  We scan through the lists of cities and carriers looking
+for the closest one.  We then move toward that item's location.
+The nearest landing field must be within the object's range.
+*/
+
+void
+move_land (obj)
+piece_info_t *obj;
+{
+       void move_to_dest();
+
+       long best_dist, best_loc;
+       long new_dist;
+       piece_info_t *p;
+
+       best_dist = find_nearest_city (obj->loc, USER, &best_loc);
+
+       for (p = user_obj[CARRIER]; p != NULL; p = p->piece_link.next) {
+               new_dist = dist (obj->loc, p->loc);
+               if (new_dist < best_dist) {
+                       best_dist = new_dist;
+                       best_loc = p->loc;
+               }
+       }
+       if (best_dist == 0) obj->moved += 1; /* fighter is on a city */
+       
+       else if (best_dist <= obj->range)
+               move_to_dest (obj, best_loc);
+               
+       else obj->func = NOFUNC; /* can't reach city or carrier */
+}
+
+/*
+Move a piece in the specified direction if possible.
+If the object is a fighter which has travelled for half its range,
+we wake it up.
+*/
+
+void move_dir (obj)
+piece_info_t *obj;
+{
+       long loc;
+       int dir;
+
+       dir = MOVE_DIR (obj->func);
+       loc = obj->loc + dir_offset[dir];
+
+       if (good_loc (obj, loc))
+               move_obj (obj, loc);
+}
+
+/*
+Move a piece toward a specified destination if possible.  For each
+direction, we see if moving in that direction would bring us closer
+to our destination, and if there is nothing in the way.  If so, we
+move in the first direction we find.
+*/
+
+void move_path (obj)
+piece_info_t *obj;
+{
+       if (obj->loc == obj->func)
+               obj->func = NOFUNC;
+       else move_to_dest (obj, obj->func);
+}
+
+/*
+Move a piece toward a specific destination.  We first map out
+the paths to the destination, if we can't get there, we return.
+Then we mark the paths to the destination.  Then we choose a
+move.
+*/
+
+void move_to_dest (obj, dest)
+piece_info_t *obj;
+long dest;
+{
+       path_map_t path_map[MAP_SIZE];
+       int fterrain;
+       char *mterrain;
+       long new_loc;
+       
+       switch (obj->type) {
+       case ARMY:
+               fterrain = T_LAND;
+               mterrain = "+";
+               break;
+       case FIGHTER:
+               fterrain = T_AIR;
+               mterrain = "+.O";
+               break;
+       default:
+               fterrain = T_WATER;
+               mterrain = ".O";
+               break;
+       }
+       
+       new_loc = vmap_find_dest (path_map, user_map, obj->loc, dest,
+                                  USER, fterrain);
+       if (new_loc == obj->loc) return; /* can't get there */
+       
+       vmap_mark_path (path_map, user_map, dest);
+       new_loc = vmap_find_dir (path_map, user_map, obj->loc, mterrain, " .");
+       if (new_loc == obj->loc) return; /* can't move ahead */
+       ASSERT (good_loc (obj, new_loc));
+       move_obj (obj, new_loc); /* everything looks good */
+}
+
+/*
+Ask the user to move her piece.
+*/
+
+void ask_user (obj)
+piece_info_t *obj;
+{
+       void user_skip(), user_fill(), user_dir(), user_set_dir();
+       void user_wake(), user_set_city_func(), user_cancel_auto();
+       void user_redraw(), user_random(), user_land(), user_sentry();
+       void user_help(), reset_func(), user_explore();
+       void user_build(), user_transport();
+       void user_armyattack(), user_repair();
+
+       char c;
+
+    for (;;) {
+       display_loc_u (obj->loc); /* display piece to move */
+       describe_obj (obj); /* describe object to be moved */
+       display_score (); /* show current score */
+       display_loc_u (obj->loc); /* reposition cursor */
+
+       c = get_chx (); /* get command from user (no echo) */
+       switch (c) {
+       case 'Q': user_dir (obj, NORTHWEST); return;
+       case 'W': user_dir (obj, NORTH); return;
+       case 'E': user_dir (obj, NORTHEAST); return;
+       case 'D': user_dir (obj, EAST); return;
+       case 'C': user_dir (obj, SOUTHEAST); return;
+       case 'X': user_dir (obj, SOUTH); return;
+       case 'Z': user_dir (obj, SOUTHWEST); return;
+       case 'A': user_dir (obj, WEST); return;
+
+       case 'J': edit (obj->loc); reset_func (obj); return;
+       case 'V': user_set_city_func (obj); reset_func (obj); return;
+       
+       case ' ': user_skip (obj); return;
+       case 'F': user_fill (obj); return;
+       case 'I': user_set_dir (obj); return;
+       case 'R': user_random (obj); return;
+       case 'S': user_sentry (obj); return;
+       case 'L': user_land (obj); return;
+       case 'G': user_explore (obj); return;
+       case 'T': user_transport (obj); return;
+       case 'U': user_repair (obj); return;
+       case 'Y': user_armyattack (obj); return;
+
+       case 'B': user_build (obj); break;
+       case 'H': user_help (); break;
+       case 'K': user_wake (obj); break;
+       case 'O': user_cancel_auto (); break;
+       case '\014':
+       case 'P': user_redraw (); break;
+       case '?': describe_obj (obj); break;
+
+       default: (void) beep ();
+       }
+    }
+}
+
+/*
+Here, if the passed object is on a city, we assign
+the city's function to the object.  However, we then awaken the
+object if necessary because the user only changed the city
+function, and did not tell us what to do with the object.
+*/
+
+void
+reset_func (obj)
+piece_info_t *obj;
+{
+       city_info_t *cityp;
+       
+       cityp = find_city (obj->loc);
+
+       if (cityp != NULL)
+       if (cityp->func[obj->type] != NOFUNC) {
+               obj->func = cityp->func[obj->type];
+               (void) awake (obj);
+       } 
+}
+
+/*
+Increment the number of moves a piece has used.  If the piece
+is an army and the army is in a city, move the army to
+the city.
+*/
+
+void
+user_skip (obj)
+piece_info_t *obj;
+{
+       void move_army_to_city();
+
+       if (obj->type == ARMY && user_map[obj->loc].contents == 'O')
+               move_army_to_city (obj, obj->loc);
+       else obj->moved++;
+}
+
+/*
+Put an object in FILL mode.  The object must be either a transport
+or carrier.  If not, we beep at the user.
+*/
+
+void
+user_fill (obj)
+piece_info_t *obj;
+{
+       if (obj->type != TRANSPORT && obj->type != CARRIER) (void) beep ();
+       else obj->func = FILL;
+}
+
+/*
+Print out help information.
+*/
+
+void
+user_help () {
+       char c;
+
+       help (help_user, user_lines);
+       prompt ("Press any key to continue: ");
+       c = get_chx ();
+       c = c; /* keep lint happy */
+}
+
+/*
+Set an object's function to move in a certain direction.
+*/
+
+void
+user_set_dir (obj)
+piece_info_t *obj;
+{
+       char c;
+
+       c = get_chx ();
+       switch (c) {
+       case 'Q': obj->func = MOVE_NW; break;
+       case 'W': obj->func = MOVE_N ; break;
+       case 'E': obj->func = MOVE_NE; break;
+       case 'D': obj->func = MOVE_E ; break;
+       case 'C': obj->func = MOVE_SE; break;
+       case 'X': obj->func = MOVE_S ; break;
+       case 'Z': obj->func = MOVE_SW; break;
+       case 'A': obj->func = MOVE_W ; break;
+       default: (void) beep (); break;
+       }
+}
+
+/*
+Wake up the current piece.
+*/
+
+void
+user_wake (obj)
+piece_info_t *obj;
+{
+       obj->func = NOFUNC;
+}
+
+/*
+Set the piece's func to random.  
+*/
+
+void
+user_random (obj)
+piece_info_t *obj;
+{
+       obj->func = RANDOM;
+}
+
+/*
+Set a piece's function to sentry.
+*/
+
+void
+user_sentry (obj)
+piece_info_t *obj;
+{
+       obj->func = SENTRY;
+}
+
+/*
+Set a fighter's function to land at the nearest city.
+*/
+
+void
+user_land (obj)
+piece_info_t *obj;
+{
+       if (obj->type != FIGHTER) (void) beep ();
+       else obj->func = LAND;
+}
+
+/*
+Set the piece's func to explore.
+*/
+
+void
+user_explore (obj)
+piece_info_t *obj;
+{
+       obj->func = EXPLORE;
+}
+
+/*
+Set an army's function to WFTRANSPORT.
+*/
+
+void
+user_transport (obj)
+piece_info_t *obj;
+{
+       if (obj->type != ARMY) (void) beep ();
+       else obj->func = WFTRANSPORT;
+}
+
+/*
+Set an army's function to ARMYATTACK.
+*/
+
+void
+user_armyattack (obj)
+piece_info_t *obj;
+{
+       if (obj->type != ARMY) (void) beep ();
+       else obj->func = ARMYATTACK;
+}
+
+/*
+Set a ship's function to REPAIR.
+*/
+
+void
+user_repair (obj)
+piece_info_t *obj;
+{
+       if (obj->type == ARMY || obj->type == FIGHTER) (void) beep ();
+       else obj->func = REPAIR;
+}
+
+/*
+Set a city's function.
+*/
+
+void
+user_set_city_func (obj)
+piece_info_t *obj;
+{
+       void e_city_fill(), e_city_explore(), e_city_stasis();
+       void e_city_wake(), e_city_random(), e_city_repair();
+       void e_city_attack();
+       
+       int type;
+       char e;
+       city_info_t *cityp;
+
+       cityp = find_city (obj->loc);
+       if (!cityp || cityp->owner != USER) {
+               (void) beep ();
+               return;
+       }
+
+       type = get_piece_name();
+       if (type == NOPIECE) {
+               (void) beep ();
+               return;
+       }
+       
+       e = get_chx ();
+       
+       switch (e) {
+       case 'F': /* fill */
+               e_city_fill (cityp, type);
+               break;
+       case 'G': /* explore */
+               e_city_explore (cityp, type);
+               break;
+       case 'I': /* directional stasis */
+               e_city_stasis (cityp, type);
+               break;
+       case 'K': /* turn off function */
+               e_city_wake (cityp, type);
+               break;
+       case 'R': /* make piece move randomly */
+               e_city_random (cityp, type);
+               break;
+       case 'U': /* repair ship */
+               e_city_repair (cityp, type);
+               break;
+       case 'Y': /* set army func to attack */
+               e_city_attack (cityp, type);
+               break;
+       default: /* bad command? */
+               (void) beep ();
+               break;
+       }
+}
+
+/*
+Change a city's production.
+*/
+
+void
+user_build (obj)
+piece_info_t *obj;
+{
+       city_info_t *cityp;
+
+       if (user_map[obj->loc].contents != 'O') { /* no user city here? */
+               (void) beep ();
+               return;
+       }
+       cityp = find_city (obj->loc);
+       ASSERT (cityp != NULL);
+       set_prod (cityp);
+}
+
+/*
+Move a piece in the direction specified by the user.
+This routine handles attacking objects.
+*/
+
+void
+user_dir (obj, dir)
+piece_info_t *obj;
+int dir;
+{
+       void user_dir_army(), user_dir_fighter(), user_dir_ship();
+
+       long loc;
+
+       loc = obj->loc + dir_offset[dir];
+
+       if (good_loc (obj, loc)) {
+               move_obj (obj, loc);
+               return;
+       }
+       if (!map[loc].on_board) {
+               error ("You cannot move to the edge of the world.");
+               delay ();
+               return;
+       }
+       switch (obj->type) {
+       case ARMY: user_dir_army (obj, loc); break;
+       case FIGHTER: user_dir_fighter (obj, loc); break;
+       default: user_dir_ship (obj, loc); break;
+       }
+}
+
+/*
+We have an army that wants to attack something or move onto some
+unreasonable terrain.  We check for errors, question the user if
+necessary, and attack if necessary.
+*/
+
+void
+user_dir_army (obj, loc)
+piece_info_t *obj;
+long loc;
+{
+       void fatal();
+       
+       int enemy_killed;
+       
+       enemy_killed = FALSE;
+
+       if (user_map[loc].contents == 'O') /* attacking own city */
+               move_army_to_city (obj, loc);
+
+       else if (user_map[loc].contents == 'T') /* transport full? */
+               fatal (obj, loc,
+       "Sorry, sir.  There is no more room on the transport.  Do you insist? ",
+       "Your army jumped into the briny and drowned.");
+
+       else if (map[loc].contents == '.') { /* going for a swim? */
+               if (!getyn ( /* thanks to Craig Hansen for this next message */
+       "Troops can't walk on water, sir.  Do you really want to go to sea? "))
+               return;
+
+               if (user_map[obj->loc].contents == 'T')
+                       comment ("Your army jumped into the briny and drowned.");
+
+               else if (user_map[loc].contents == '.')
+                       comment ("Your army marched dutifully into the sea and drowned.");
+
+               else { /* attack something at sea */
+                       enemy_killed = islower (user_map[loc].contents);
+                       attack (obj, loc);
+       
+                       if (obj->hits > 0) /* ship won? */
+                       comment ("Your army regretfully drowns after its successful assault.");
+               }
+               if (obj->hits > 0) {
+                       kill_obj (obj, loc);
+                       if (enemy_killed) scan (comp_map, loc);
+               }
+       }
+               
+       else if (isupper (user_map[loc].contents)
+               && user_map[loc].contents != 'X') { /* attacking self */
+               if (!getyn (
+       "Sir, those are our men!  Do you really want to attack them? "))
+               return;
+
+               attack (obj, loc);
+       }
+
+       else attack (obj, loc);
+}
+
+/*
+Here we have a fighter wanting to attack something.  There are only
+three cases:  attacking a city, attacking ourself, attacking the enemy.
+*/
+
+void
+user_dir_fighter (obj, loc)
+piece_info_t *obj;
+long loc;
+{
+       if (map[loc].contents == '*')
+               fatal (obj, loc,
+       "That's never worked before, sir.  Do you really want to try? ",
+       "Your fighter was shot down.");
+
+       else if (isupper (user_map[loc].contents)) {
+               if (!getyn (
+       "Sir, those are our men!  Do you really want to attack them? "))
+               return;
+
+               attack (obj, loc);
+       }
+
+       else attack (obj, loc);
+}
+
+/*
+Here we have a ship attacking something, or trying to move on
+shore.  Our cases are: moving ashore (and subcases), attacking
+a city, attacking self, attacking enemy.
+*/
+       
+void
+user_dir_ship (obj, loc)
+piece_info_t *obj;
+long loc;
+{
+       int enemy_killed;
+
+       enemy_killed = FALSE;
+
+       if (map[loc].contents == '*') {
+               (void) sprintf (jnkbuf, "Your %s broke up on shore.",
+                               piece_attr[obj->type].name);
+
+               fatal (obj, loc,
+       "That's never worked before, sir.  Do you really want to try? ",
+                       jnkbuf);
+       }
+
+       else if (map[loc].contents == '+') { /* moving ashore? */
+               if (!getyn (
+       "Ships need sea to float, sir.  Do you really want to go ashore? "))
+               return;
+
+               if (user_map[loc].contents == '+')
+                       comment ("Your %s broke up on shore.",
+                                piece_attr[obj->type].name);
+
+               else { /* attack something on shore */
+                       enemy_killed = islower (user_map[loc].contents);
+                       attack (obj, loc);
+
+                       if (obj->hits > 0) /* ship won? */
+                               comment ("Your %s breaks up after its successful assault.",
+                                        piece_attr[obj->type].name);
+               }
+               if (obj->hits > 0) {
+                       kill_obj (obj, loc);
+                       if (enemy_killed) scan (comp_map, loc);
+               }
+       }
+               
+       else if (isupper (user_map[loc].contents)) { /* attacking self */
+               if (!getyn (
+       "Sir, those are our men!  Do you really want to attack them? "))
+               return;
+
+               attack (obj, loc);
+       }
+
+       else attack (obj, loc);
+}
+
+/*
+Here a user wants to move an army to a city.  If the city contains
+a non-full transport, we make the move.  Otherwise we ask the user
+if she really wants to attack the city.
+*/
+
+void
+move_army_to_city (obj, city_loc)
+piece_info_t *obj;
+long city_loc;
+{
+       piece_info_t *tt;
+
+       tt = find_nfull (TRANSPORT, city_loc);
+
+       if (tt != NULL) move_obj (obj, city_loc);
+
+       else fatal (obj, city_loc,
+       "That's our city, sir!  Do you really want to attack the garrison? ",
+       "Your rebel army was liquidated.");
+}
+
+/*
+Cancel automove mode.
+*/
+
+void
+user_cancel_auto () {
+       if (!automove)
+               comment ("Not in auto mode!");
+       else {
+               automove = FALSE;
+               comment ("Auto mode cancelled.");
+       }
+}
+
+/*
+Redraw the screen.
+*/
+
+void
+user_redraw () {
+       redraw ();
+}
+
+/*
+Awaken an object if it needs to be.  Normally, objects are awakened
+when they are next to an enemy object or an unowned city.  Armies
+on troop transports are not awakened if they are surrounded by sea.
+We return true if the object is now awake.  Objects are never
+completely awoken here if their function is a destination.  But we
+will return TRUE if we want the user to have control.
+*/
+
+int
+awake (obj)
+piece_info_t *obj;
+{
+       int i;
+       char c;
+       long t;
+
+       if (obj->type == ARMY && vmap_at_sea (user_map, obj->loc)) {
+           obj->moved = piece_attr[ARMY].range;
+           return (FALSE);
+       }
+       if (obj->func == NOFUNC) return (TRUE); /* object is awake */
+       
+       if (obj->type == FIGHTER /* wake fighters */
+           && obj->func != LAND /* that aren't returning to base */
+           && obj->func < 0 /* and which don't have a path */
+           && obj->range <= find_nearest_city (obj->loc, USER, &t) + 2) {
+               obj->func = NOFUNC; /* wake piece */
+               return (TRUE);
+       }
+       for (i = 0; i < 8; i++) { /* for each surrounding cell */
+               c = user_map[obj->loc+dir_offset[i]].contents;
+
+               if (islower (c) || c == '*' || c == 'X') {
+                       if (obj->func < 0) obj->func = NOFUNC; /* awaken */
+                       return (TRUE);
+               }
+       }
+       return (FALSE);
+}
+
+/*
+Question the user about a fatal move.  If the user responds 'y',
+print out the response and kill the object.
+*/
+
+void
+fatal (obj, loc, message, response)
+piece_info_t *obj;
+long loc;
+char *message;
+char *response;
+{
+       if (getyn (message)) {
+               comment (response);
+               kill_obj (obj, loc);
+       }
+}