--- /dev/null
+/* %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 ();
+}
--- /dev/null
+/* %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);
+}
--- /dev/null
+/* %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);
+ }
+}