From: Eric S. Raymond Date: Thu, 1 Dec 1994 09:56:26 +0000 (+0000) Subject: Initial revision X-Git-Tag: 1.0~7 X-Git-Url: http://thyrsus.com/gitweb/?a=commitdiff_plain;h=7d1c411068b704fdcd3921d2cfd4cd8ea5380408;p=vms-empire.git Initial revision --- diff --git a/edit.c b/edit.c new file mode 100644 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 +#else +#include +#endif + +#include +#include +#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 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 +#else +#include +#endif + +#include +#include +#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 index 0000000..865251d --- /dev/null +++ b/usermove.c @@ -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 +#include +#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); + } +}