[comp.sources.games] v04i091: xconq5 - version 5 of the strategy game for X-windows and curses, Part03/18

games@tekred.TEK.COM (06/29/88)

Submitted by: "Stanley T. Shebs" <shebs%defun@cs.utah.edu>
Comp.sources.games: Volume 4, Issue 91
Archive-name: xconq5/Part03



#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 3 (of 18)."
# Contents:  draw.c lib/four.map mplay.c
# Wrapped by billr@saab on Wed Jun 29 08:55:32 1988
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f draw.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"draw.c\"
else
echo shar: Extracting \"draw.c\" \(16834 characters\)
sed "s/^X//" >draw.c <<'END_OF_draw.c'
X/* Copyright (c) 1987, 1988  Stanley T. Shebs, University of Utah. */
X/* This program may be used, copied, modified, and redistributed freely */
X/* for noncommercial purposes, so long as this notice remains intact. */
X
X/* RCS $Header: draw.c,v 1.1 88/06/21 12:30:08 shebs Exp $ */
X
X/* Geometry is somewhat tricky because our viewports are supposed */
X/* to wrap around a cylinder transparently.  The general idea is that */
X/* if modulo opns map x coordinates onto a cylinder, adding and subtracting */
X/* the diameter of the cylinder "un-modulos" things.  If this doesn't make */
X/* any sense to you, then be careful about fiddling with the code! */
X
X/* If that wasn't bad enough, the hexes constitute an oblique coordinate */
X/* where the axes form a 60 degree angle to each other.  Fortunately, no */
X/* trig is necessary - to convert to/from rectangular, add/subtract 1/2 of */
X/* the y coordinate to x, and leave the y coordinate alone. */
X
X/* The graphical code uses mostly text drawing functions, which are more */
X/* likely to be efficient than is random bitblting.  (The interface may */
X/* implement the operations as random blitting, but that's OK.) */
X
X#include "config.h"
X#include "misc.h"
X#include "period.h"
X#include "side.h"
X#include "unit.h"
X#include "map.h"
X
Xextern bool populations;        /* used to decide about running pop display */
X
Xchar rowbuf[BUFSIZE];           /* buffer for terrain row drawing */
X
X/* Completely redo a screen, making no assumptions about appearance. */
X/* This one is used frequently, especially when a window is exposed. */
X
Xredraw(side)
XSide *side;
X{
X    if (active_display(side)) {
X	erase_cursor(side);
X	clear_window(side, side->main);
X	show_note(side);
X	show_info(side);
X	show_prompt(side);
X	show_all_sides(side);
X	show_timemode(side);
X	show_clock(side);
X	show_state(side);
X	show_map(side);
X	show_world(side);
X	flush_output(side);
X	flush_input(side);
X    }
X}
X
X/* Ensure that given location is visible.  We also flush the input because */
X/* any input relating to a different screen is probably worthless. */
X
Xput_on_screen(side, x, y)
XSide *side;
Xint x, y;
X{
X    /* Ugly hack to prevent extra boxes being drawn during init - don't ask!*/
X    if (x == 0 && y == 0) return;
X    if (active_display(side)) {
X	if (!in_middle(side, x, y)) {
X	    side->vcx = wrap(x);
X	    side->vcy = min(max(side->vh2-(1-(side->vh&1)), y),
X			    (world.height-1)-side->vh2);
X	    if (side->lastvcx >= 0) undraw_box(side);
X	    show_map(side);
X	    flush_output(side);
X            flush_input(side);
X	}
X    }
X}
X
X/* Decide whether given location is not too close to edge of screen. */
X/* We do this because it's a pain to move units when half the adjacent */
X/* places aren't even visible.  This routine effectively places a lower */
X/* limit of 5x5 for the map window. (I think) */
X
Xin_middle(side, x, y)
XSide *side;
Xint x, y;
X{
X    int vcx = side->vcx, vcy = side->vcy;
X    int vw2 = side->vw2, vh2 = side->vh2;
X
X    if (!between(vcy-vh2+2, y, vcy+vh2-2) && between(2, y, world.height-3))
X	return FALSE;
X    x = unwrap(side, x + (y - vcy) / 2);
X    return between(vcx-vw2+2, x, vcx+vw2-3+(side->vw&1));
X}
X
X/* Transform map coordinates into screen coordinates, relative to the given */
X/* side.  Allow for cylindricalness and number of pixels in a hex. */
X
Xxform(side, x, y, sxp, syp)
XSide *side;
Xint x, y, *sxp, *syp;
X{
X    *sxp = ((side->hw * (x - (side->vcx - side->vw2))) +
X	    (side->hw * (y - side->vcy)) / 2);
X    *syp = side->hch * ((side->vcy + side->vh2) - y);
X}
X
X/* Undo the wrapping effect, relative to viewport location. */
X/* Note that both conditions cannot both be true at the same time, */
X/* since viewport is smaller than map. */
X
Xunwrap(side, x)
XSide *side;
Xint x;
X{
X    int vcx = side->vcx, vw2 = side->vw2, vw34 = side->vw2 + (side->vw2 >> 1);
X
X    if (vcx - vw2 < 0 && x > vcx + vw34) x -= world.width;
X    if (vcx + vw2 > world.width-1 && x < vcx - vw34) x += world.width;
X    return x;
X}
X
X/* Un-transform screen coordinates (as supplied by mouse perhaps) into */
X/* map coordinates.  This doesn't actually account for the details of */
X/* hexagonal boundaries, and actually discriminates box-shaped areas. */
X
Xdeform(side, sx, sy, xp, yp)
XSide *side;
Xint sx, sy, *xp, *yp;
X{
X    int vcx = side->vcx, vcy = side->vcy, adjust;
X
X    *yp = (vcy + side->vh2) - (sy / side->hch);
X    adjust = (((*yp - vcy) & 1) ? ((side->hw/2) * (*yp >= vcy ? 1 : -1)) : 0);
X    *xp = wrap(((sx - adjust) /	side->hw) - (*yp - vcy) / 2 +
X	       (vcx - side->vw2));
X}
X
X/* Transform coordinates in the world display.  Simpler, since no moving */
X/* viewport nonsense. */
X
Xw_xform(side, x, y, sxp, syp)
XSide *side;
Xint x, y, *sxp, *syp;
X{
X    *sxp = side->mm * x + (side->mm * y) / 2;
X    *syp = side->mm * (world.height - 1 - y);
X}
X
X/* Redraw the map of the whole world.  We use square blobs instead of icons, */
X/* since individual "hexes" may be as little as 1x1 pixels in size, and */
X/* there are lots of them.  Algorithm uses run-length encoding to find and */
X/* draw bars of constant color.  Monochrome just draws units - no good */
X/* choices for terrain display. */
X
Xshow_world(side)
XSide *side;
X{
X    int x, y, color, barcolor, x1;
X
X    if (active_display(side) && world_display(side)) {
X	clear_window(side, side->world);
X	for (y = world.height-1; y >= 0; --y) {
X	    x1 = 0;
X	    barcolor = world_color(side, x1, y);
X	    for (x = 0; x < world.width; ++x) {
X		color = world_color(side, x, y);
X		if (color != barcolor) {
X		    draw_bar(side, x1, y, x - x1, barcolor);
X		    x1 = x;
X		    barcolor = color;
X		}
X	    }
X	    draw_bar(side, x1, y, world.width - x1, barcolor);
X	}
X	if (side->vcy >= 0) draw_box(side);
X    }
X}
X
X/* Compute the color representing a hex from the given side's point of view. */
X
Xworld_color(side, x, y)
XSide *side;
Xint x, y;
X{
X    int view = side_view(side, x, y);
X    Side *side2;
X
X    if (side->monochrome) {
X	return ((view == UNSEEN || view == EMPTY) ? side->bgcolor :
X		                                    side->fgcolor);
X    } else {
X	if (view == UNSEEN) {
X	    return (side->bgcolor);
X	} else if (view == EMPTY) {
X	    return (side->hexcolor[terrain_at(x, y)]);
X	} else {
X	    side2 = side_n(vside(view));
X	    return ((side2 == NULL) ? side->neutcolor :
X		    (allied_side(side2, side) ? side->altcolor :
X		     side->enemycolor));
X	}
X    }
X}
X
X/* Draw an outline box on the world map.  Since we adopt the dubious trick */
X/* of inverting through all planes, must be careful to undo before moving; */
X/* also, draw/undraw shiftedly so both boxes appear on both sides of world. */
X
Xdraw_box(side)
XSide *side;
X{
X    invert_box(side, side->vcx, side->vcy);
X    invert_box(side, side->vcx - world.width, side->vcy);
X    side->lastvcx = side->vcx;  side->lastvcy = side->vcy;
X}
X
Xundraw_box(side)
XSide *side;
X{
X    invert_box(side, side->lastvcx, side->lastvcy);
X    invert_box(side, side->lastvcx - world.width, side->lastvcy);
X}
X
X/* Draw immediate area in more detail.  We make a little effort to avoid */
X/* drawing hexes off the visible part of the screen, but are still somewhat */
X/* conservative, so as not to get holes in the display.  Implication is that */
X/* some lower level of routines has to be able to clip the map window. */
X
Xshow_map(side)
XSide *side;
X{
X    int y1, y2, y, x1, x2, adj;
X
X    if (active_display(side)) {
X	clear_window(side, side->map);
X	y1 = side->vcy + side->vh2;
X	y2 = side->vcy - side->vh2 + 1 - (side->vh & 1);
X	for (y = y1; y >= y2; --y) {
X	    adj = (y - side->vcy) / 2;
X	    x1 = side->vcx - side->vw2 - adj - 1;
X	    x2 = side->vcx + side->vw2 - adj + 1 + (side->vw & 1);
X	    draw_row(side, x1, y, x2 - x1);
X	}
X	draw_cursor(side);
X	flush_output(side);
X	draw_box(side);  /* must be after flush */
X    }
X}
X
X/* Draw an individual detailed hex, as a row of one. */
X/* This routine may be called in cases where the hex is not on the main */
X/* screen;  if so, then the world map but not the local map is drawn on. */
X/* (should the display be shifted to make visible?) */
X
Xdraw_hex(side, x, y, flushit)
XSide *side;
Xint x, y;
Xbool flushit;
X{
X    int sx, sy;
X
X    if (active_display(side)) {
X	if (side->monochrome || side->showmode == TERRICONS) {
X	    xform(side, unwrap(side, x), y, &sx, &sy);
X	    draw_hex_icon(side, side->map, sx, sy, side->bgcolor, HEX);
X	}
X	draw_row(side, unwrap(side, x), y, 1);
X	draw_bar(side, x, y, 1, world_color(side, x, y));
X	if (flushit) flush_output(side);
X    }
X}
X
X/* The basic map drawing routine does an entire row at a time, which yields */
X/* order-of-magnitude speedups (!).  This routine is complicated by several */
X/* tricks:  1) in monochrome, the entire line can be drawn at once; 2) in */
X/* color, run-length encoding maximizes the length of constant-color strings */
X/* and 3) anything which is in the background color need not be drawn. */
X/* In general, this routine dominates the map viewing process, so efficiency */
X/* here is very important. */
X
Xdraw_row(side, x0, y0, len)
XSide *side;
Xint x0, y0, len;
X{
X    bool empty = TRUE;
X    char ch;
X    int i = 0, x, x1, color, segcolor, sx, sy;
X
X    if (side->monochrome) {
X	xform(side, x0, y0, &sx, &sy);
X	for (x = x0; x < x0 + len; ++x) {
X	    if (side_view(side, wrap(x), y0) == EMPTY) {
X		rowbuf[i++] = ttypes[terrain_at(wrap(x), y0)].tchar;
X		empty = FALSE;
X	    } else {
X		rowbuf[i++] = ' ';
X	    }
X	}
X	if (!empty) draw_terrain_row(side, sx, sy, rowbuf, i, side->fgcolor);
X    } else {
X	x1 = x0;
X	segcolor = hex_color(side, x0, y0);
X	for (x = x0; x < x0 + len; ++x) {
X	    color = hex_color(side, x, y0);
X	    if (color != segcolor) {
X		if (segcolor != side->bgcolor) {
X		    xform(side, x1, y0, &sx, &sy);
X		    draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
X		}
X		i = 0;
X		x1 = x;
X		segcolor = color;
X	    }
X	    switch(side->showmode) {
X	    case FULLHEX:
X	    case BOTHICONS:
X		ch = HEX;
X		break;
X	    case BORDERHEX:
X		ch = OHEX;
X		break;
X	    case TERRICONS:
X		ch = ttypes[terrain_at(wrap(x), y0)].tchar;
X		break;
X	    }
X	    rowbuf[i++] = ch;
X	}
X	if (len == 1) i = 1;
X	xform(side, x1, y0, &sx, &sy);
X	draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
X	if (side->showmode == BOTHICONS) {
X	    i = 0;
X	    x1 = x0;
X	    segcolor = terricon_color(side, x0, y0);
X	    for (x = x0; x < x0 + len; ++x) {
X		color = terricon_color(side, x, y0);
X		if (color != segcolor) {
X		    xform(side, x1, y0, &sx, &sy);
X		    draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
X		    i = 0;
X		    x1 = x;
X		    segcolor = color;
X		}
X		rowbuf[i++] = ttypes[terrain_at(wrap(x), y0)].tchar;
X	    }
X	    if (len == 1) i = 1;
X	    xform(side, x1, y0, &sx, &sy);
X	    draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
X	}
X    }
X    /* Units are much harder to optimize - fortunately they're sparse */
X    for (x = x0; x < x0 + len; ++x) {
X	draw_unit(side, x, y0);
X    }
X}
X
X/* Return the color of the hex. (for color displays only) */
X
Xhex_color(side, x, y)
XSide *side;
Xint x, y;
X{
X    return ((side_view(side, wrap(x), y) == UNSEEN) ? side->bgcolor :
X	    side->hexcolor[terrain_at(wrap(x), y)]);
X}
X
X/* Return the color of a terrain icon overlaying a colored hex. */
X
Xterricon_color(side, x, y)
XSide *side;
Xint x, y;
X{
X    return ((side_view(side, wrap(x), y) == UNSEEN) ? side->bgcolor :
X	    (ttypes[terrain_at(wrap(x), y)].dark ? side->fgcolor :
X	     side->bgcolor));
X}
X
X/* Draw a single unit icon as appropriate.  This *also* has a bunch of */
X/* details to worry about: centering of icon in hex, clearing a rectangular */
X/* area for the icon, picking a color for the unit, using either a bitmap */
X/* or font char, and adding a side number for many-player games. */
X/* Must also be careful not to draw black-on-black for units in space. */
X/* This routine has also been drafted into drawing populace side numbers */
X/* for otherwise empty hexes. */
X
Xdraw_unit(side, x, y)
XSide *side;
Xint x, y;
X{
X    int view = side_view(side, wrap(x), y), sx, sy, ucolor, hcolor, n;
X    int terr = terrain_at(wrap(x), y), pop, pcolor;
X    Side *side2;
X
X    if (view != UNSEEN) {
X	if (view == EMPTY) {
X	    if (populations) {
X		pop = people_at(wrap(x), y);
X		if (pop != NOBODY) {
X		    side2 = side_n(pop-8);
X		    pcolor = (allied_side(side, side2) ? side->owncolor :
X			      (enemy_side(side, side2) ? side->enemycolor :
X			       side->neutcolor));
X		    if (pcolor == side->owncolor && 
X			(ttypes[terr].dark ||
X			 side->monochrome ||
X			 (side->showmode == TERRICONS)))
X			pcolor = side->fgcolor;
X		    xform(side, x, y, &sx, &sy);
X		    draw_side_number(side, side->map, sx, sy, pop-8, pcolor);
X		}
X	    }
X	} else {
X	    xform(side, x, y, &sx, &sy);
X	    side2 = side_n(vside(view));
X	    ucolor = (allied_side(side, side2) ? side->owncolor :
X		      (enemy_side(side, side2) ? side->enemycolor :
X		       side->neutcolor));
X	    if (ucolor == side->owncolor && 
X		(ttypes[terr].dark ||
X		 side->monochrome ||
X		 (side->showmode == TERRICONS)))
X		ucolor = side->fgcolor;
X	    if (side->monochrome && side != side2)
X		ucolor = side->bgcolor;
X	    hcolor = (side == side2 ? side->bgcolor : side->fgcolor);
X	    if (side->monochrome) {
X		/* erasing background */
X		draw_hex_icon(side, side->map, sx, sy, hcolor, HEX);
X	    } else if (side->showmode != TERRICONS) {
X		draw_hex_icon(side, side->map, sx, sy, hex_color(side, x, y),
X			      ((side->showmode == BORDERHEX) ? OHEX : HEX));
X	    }
X	    draw_unit_icon(side, side->map, sx, sy, vtype(view), ucolor);
X	    n = side_number(side2);
X	    if ((numsides > 2 || side->monochrome) && n != side_number(side)) {
X		draw_side_number(side, side->map, sx, sy, n, ucolor);
X	    }
X	}
X    }
X}
X
X/* Cursor drawing also draws the unit in some other color if it's not the */
X/* "top-level" unit in a hex, as well as getting the player's attention */
X/* if the new location is sufficiently far from the last. */
X
Xdraw_cursor(side)
XSide *side;
X{
X    int sx, sy;
X
X    if (active_display(side)) {
X	/* ugly hack to prevent extra cursor draw */
X	if (side->cury == 0) return;
X	xform(side, unwrap(side, side->curx), side->cury, &sx, &sy);
X	if (side->curunit != NULL && side->curunit->transport != NULL) {
X	    if (side->monochrome) {
X		draw_hex_icon(side, side->map, sx, sy, side->bgcolor, HEX);
X		draw_unit_icon(side, side->map, sx, sy,
X			       side->curunit->type, side->fgcolor);
X	    } else {
X		draw_unit_icon(side, side->map, sx, sy,
X			       side->curunit->type, side->diffcolor);
X	    }
X	}
X	/* Flash something to draw the eye a long ways */
X	if (humanside(side)) {
X	    if (distance(side->curx, side->cury, side->lastx, side->lasty) > 3)
X		flash_position(side, sx, sy);
X	    draw_cursor_icon(side, sx, sy);
X	    side->lastx = side->curx;  side->lasty = side->cury;
X	}
X    }
X}
X
X/* Get rid of cursor by redrawing the hex. */
X
Xerase_cursor(side)
XSide *side;
X{
X    if (side->lasty > 0) draw_hex(side, side->lastx, side->lasty, TRUE);
X}
X
X/* Draw a splat visible to both sides at a given location.  Several splats */
X/* available, depending on the seriousness of the hit.  Make an extra-flashy */
X/* display when The Bomb goes off.  Because of the time delays involved, we */
X/* have to update both sides' displays more or less simultaneously. Would be */
X/* better to exhibit to all sides maybe, but I'm not going to bother! */
X
Xdraw_blast(unit, es, hit)
XUnit *unit;
XSide *es;
Xint hit;
X{
X    char ch;
X    int ux = unit->x, uy = unit->y, sx, sy, i;
X    Side *us = unit->side;
X
X    if (hit >= period.nukehit) {
X	if (active_display(us)) invert_whole_map(us);
X	if (active_display(es)) invert_whole_map(es);
X	/* may need a time delay if X is too speedy */
X	if (active_display(us)) invert_whole_map(us);
X	if (active_display(es)) invert_whole_map(es);
X	for (i = 0; i < 4; ++i) {
X	    if (active_display(us)) draw_mushroom(us, ux, uy, i);
X	    if (active_display(es)) draw_mushroom(es, ux, uy, i);
X	    if (i != 2 && (active_display(us) || active_display(es))) sleep(1);
X	}
X	if (active_display(us) || active_display(es)) sleep(1);
X    } else {
X	ch = ((hit >= unit->hp) ? 'd' : ((hit > 0) ? 'c' : 'b'));
X	if (active_display(us)) {
X	    xform(us, unwrap(us, ux), uy, &sx, &sy);
X	    draw_blast_icon(us, us->map, sx, sy, ch, us->enemycolor);
X	    flush_output(us);
X	}
X	if (active_display(es)) {
X	    xform(es, unwrap(es, ux), uy, &sx, &sy);
X	    draw_blast_icon(es, es->map, sx, sy, ch, es->owncolor);
X	    flush_output(es);
X	}
X    }
X}
X
X/* Draw all the units in a column.  They should be spaced so they don't */
X/* overlap or get misaligned with text, and can be inverted if desired. */
X
Xdraw_unit_list(side, hilite)
XSide *side;
Xbool hilite[];
X{
X    int u, pos = 0, spacing = max(side->hh, side->fh);
X    
X    if (active_display(side)) {
X	for_all_unit_types(u) {
X	    draw_hex_icon(side, side->state, side->margin, pos,
X			  (hilite[u] ? side->fgcolor : side->bgcolor), OHEX);
X	    draw_unit_icon(side, side->state, side->margin, pos, u,
X			   (hilite[u] ? side->bgcolor : side->fgcolor));
X	    pos += spacing;
X	}
X    }
X}
END_OF_draw.c
if test 16834 -ne `wc -c <draw.c`; then
    echo shar: \"draw.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f lib/four.map -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"lib/four.map\"
else
echo shar: Extracting \"lib/four.map\" \(1621 characters\)
sed "s/^X//" >lib/four.map <<'END_OF_lib/four.map'
XXconq 0 --+--+
XMap 184 30 100 1 0
X184_
X184.
X184.
X184.
X39.4,42.4,42.4,42.4,3.
X39.4+,41.4+,41.4+,41.4+,2.
X,+,,+34.3,3+,,+,,+34.3,3+,,+,,+34.3,3+,,+,,+34.3,3+1,
X+,.3,33.,7+,.3,33.,7+,.3,33.,7+,.3,33.,6+
X+5.%,,32.,5+5.%,,32.,5+5.%,,32.,5+5.%,,32.,4+
X..,..5,30.,~~^++..,..5,30.,~~^++..,..5,30.,~~^++..,..5,30.,~~^2+
X++.,.,,3%31.,~~^^++.,.,,3%31.,~~^^++.,.,,3%31.,~~^^++.,.,,3%31.,~~2^
X3+3.,%^+33.~^^3+3.,%^+33.~^^3+3.,%^+33.~^^3+3.,%^+33.~2^
X++,4.,,+%,30.+~^3+,4.,,+%,30.+~^3+,4.,,+%,30.+~^3+,4.,,+%,30.+~^1+
X^^3+4.,,%,28.4~3^3+4.,,%,28.4~3^3+4.,,%,28.4~3^3+4.,,%,28.4~1^
X^_^++8.,+,27.~~^^_^++8.,+,27.~~^^_^++8.,+,27.~~^^_^++8.,+,27.~~1^
X3^+,,39.+3^+,,39.+3^+,,39.+3^+,,39.1+
X+^++,,+39.+^++,,+39.+^++,,+39.+^++,,+39.
X++43.,++43.,++43.,++43.1,
X++42.+,++42.+,++42.+,++42.+1,
X184.
X184.
X184.
X184.
X184.
X184.
X184.
X184.
X184.
X184.
X184_
XUnits 32 1 0
X* Far*West*North 43,23 -1
X* West*North 89,23 -1
X* Mideast*North 135,23 -1
X* East*North 181,23 -1
X* East*Harbor 0,21 -1
X* Far*West*Plains 43,21 -1
X* Far*West*Harbor 46,21 -1
X* West*Plains 89,21 -1
X* West*Harbor 92,21 -1
X* Mideast*Plains 135,21 -1
X* Mideast*Harbor 138,21 -1
X* East*Plains 181,21 -1
X* East*Island 9,17 -1
X* Far*West*Desert 42,17 -1
X* Far*West*Valley 45,17 -1
X* Far*West*Island 55,17 -1
X* West*Desert 88,17 -1
X* West*Valley 91,17 -1
X* West*Island 101,17 -1
X* Mideast*Desert 134,17 -1
X* Mideast*Valley 137,17 -1
X* Mideast*Island 147,17 -1
X* East*Desert 180,17 -1
X* East*Valley 183,17 -1
X@ West*Main 94,16 -1
X@ Far*West*Main 48,16 -1
X@ East*Main 2,16 -1
X@ Mideast*Main 140,16 -1
X* East*South 2,13 -1
X* Far*West*South 48,13 -1
X* West*South 94,13 -1
X* Mideast*South 140,13 -1
END_OF_lib/four.map
if test 1621 -ne `wc -c <lib/four.map`; then
    echo shar: \"lib/four.map\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f mplay.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"mplay.c\"
else
echo shar: Extracting \"mplay.c\" \(32778 characters\)
sed "s/^X//" >mplay.c <<'END_OF_mplay.c'
X/* Copyright (c) 1987, 1988  Stanley T. Shebs, University of Utah. */
X/* This program may be used, copied, modified, and redistributed freely */
X/* for noncommercial purposes, so long as this notice remains intact. */
X
X/* RCS $Header: mplay.c,v 1.1 88/06/21 12:30:25 shebs Exp $ */
X
X/* This file implements all of machine strategy.  Not much room for fancy */
X/* tricks, just solid basic play.  The code emphasizes avoidance of mistakes */
X/* instead of strategic brilliance, so machine behavior is akin to bulldozer */
X/* plodding.  Nevertheless, bulldozers can be very effective when they */
X/* outnumber the human players... */
X
X/* It is also very important to prevent infinite loops, so no action of the */
X/* machine player is 100% certain. */
X
X#include "config.h"
X#include "misc.h"
X#include "dir.h"
X#include "period.h"
X#include "side.h"
X#include "unit.h"
X#include "map.h"
X#include "global.h"
X
X/* Maximum number of unit groups that can be maintained.  Need groups for */
X/* both offense and defense. */
X
X#define MAXGROUPS 80
X
X/* the non-group */
X
X#define NOGROUP 0
X
X/* Group goals. */
X
X#define HITTARGET 1
X#define CAPTARGET 2
X#define OCCUPYHEX 3
X#define EXPLORE 4
X#define DEFEND 5
X
X/* Individual goals. */
X
X#define NOGOAL 0
X#define DRIFT 1
X#define ASSAULT 2
X#define DISCOVER 3
X#define LOAD 4
X#define APPROACH 5
X#define RELOAD 6
X
X/* Groups organize machine player activity at the multiple-unit level. */
X
Xtypedef struct a_group {
X    short goal;                 /* the intended purpose of the group */
X    short priority;             /* how important the group is */
X    short x, y;                 /* a relevant location */
X    short etype;                /* type of a unit there (or NOTHING) */
X    short area;                 /* radius of relevance of group activity */
X    short size;                 /* number of units in the group */
X} Group;
X
X/* This structure is where machine sides keep all the plans and planning */
X/* related data. */
X/* Group 0 is never actually used (a sort of a dummy for various purposes). */
X
Xtypedef struct a_plan {
X    short knowns[MAXSIDES];     /* estimated strength of other sides */
X    short allieds[MAXSIDES];    /* strength of other alliances */
X    short cx, cy;               /* "centroid" of all our units */
X    short shouldresign;         /* true if machine thinks it should resign */
X    short lastreplan;           /* last turn we rechecked the plans */
X    Group group[MAXGROUPS];     /* all the groups that can be formed */
X} Plan;
X
X#define side_plan(s) ((Plan *) (s)->plan)
X
X/* Malloced integer array accessors and modifers. */
X
X#define aref(m,x,y) ((m)[(x)+world.width*(y)])
X
X#define aset(m,x,y,v) ((m)[(x)+world.width*(y)] = (v))
X
X/* General collections of numbers used by all machine players. */
X
Xint bhw[MAXUTYPES][MAXUTYPES];  /* basic worth for hitting */
Xint bcw[MAXUTYPES][MAXUTYPES];  /* basic worth for capturing */
Xint bthw[MAXUTYPES][MAXUTYPES]; /* basic worth for carrying hitters */
Xint btcw[MAXUTYPES][MAXUTYPES]; /* basic worth for carrying capturers */
Xint maxoccupant[MAXUTYPES];     /* total capacity of a transport */
Xint *localworth;                /* for evaluation of nearby hexes */
X
XUnit *munit;                    /* Unit being decided about */
X
XSide *mside;                    /* Side whose unit is being decided about */
X
X/* Init used by all machine players.  Precompute useful information */
X/* relating to unit types in general, and that usually gets referenced */
X/* in inner loops. */
X
Xinit_mplayers()
X{
X    int u, u2, g;
X    Side *side;
X
X    localworth = (int *) malloc(world.width*world.height*sizeof(int));
X    for_all_unit_types(u) {
X	maxoccupant[u] = 0;
X	for_all_unit_types(u2) {
X	    bhw[u][u2] = basic_hit_worth(u, u2);
X	    bcw[u][u2] = basic_capture_worth(u, u2);
X	    bthw[u][u2] = basic_transport_worth(u, u2);
X	    btcw[u][u2] = basic_transport_worth(u, u2);
X	    maxoccupant[u] += utypes[u].capacity[u2];
X	}
X    }
X    /* tell us about how things rated */
X    if (Debug) {
X	for_all_unit_types(u) {
X	    for_all_unit_types(u2) printf("%5d", bhw[u][u2]);
X	    printf("\n");
X	}
X	printf("\n");
X	for_all_unit_types(u) {
X	    for_all_unit_types(u2) printf("%5d", bcw[u][u2]);
X	    printf("\n");
X	}
X	printf("\n");
X	for_all_unit_types(u) {
X	    for_all_unit_types(u2) printf("%5d", bthw[u][u2]);
X	    printf("\n");
X	}
X	printf("\n");
X	for_all_unit_types(u) {
X	    for_all_unit_types(u2) printf("%5d", btcw[u][u2]);
X	    printf("\n");
X	}
X	printf("\n");
X    }
X    /* For all sides, because human might use "robot" option */
X    for_all_sides(side) {
X	side->plan = (long) malloc(sizeof(Plan));
X	side_plan(side)->shouldresign = FALSE;
X	side_plan(side)->cx = side_plan(side)->cy = 0;
X	for (g = 0; g < MAXGROUPS; ++g) {
X	    side_plan(side)->group[g].goal = NOGROUP;
X	    side_plan(side)->group[g].priority = 0;
X	}
X	side_plan(side)->lastreplan = -100;
X    }
X}
X
X/* A crude estimate of the payoff of one unit type hitting on another type. */
X/* This is just for general estimation, since actual worth may depend on */
X/* damage already sustained, unit's goals, etc. */
X
Xbasic_hit_worth(u, e)
Xint u, e;
X{
X    int worth, anti;
X
X    worth = utypes[u].hit[e] * min(utypes[e].hp, utypes[u].damage[e]);
X    if (utypes[e].hp > utypes[u].damage[e]) {
X	worth /= utypes[e].hp;
X    } else {
X	worth *= utypes[e].hp;
X    }
X    if (period.counterattack) {
X	anti = utypes[e].hit[u] * min(utypes[u].hp, utypes[e].damage[u]);
X	if (utypes[u].hp > utypes[e].damage[u]) {
X	    anti /= utypes[u].hp;
X	} else {
X	    anti *= utypes[u].hp;
X	}
X    }
X    if (utypes[e].territory > 0) worth *= utypes[e].territory;
X    worth -= anti;
X    return worth;
X}
X
X/* A crude estimate of the payoff of one unit type trying to capture. */
X
Xbasic_capture_worth(u, e)
Xint u, e;
X{
X    int worth = 0, anti = 0;
X
X    if (could_capture(u, e)) {
X	worth += utypes[e].territory * utypes[u].capture[e];
X    }
X    return worth;
X}
X
X/* This should account for volume also... */
X
Xbasic_transport_worth(u, e)
Xint u, e;
X{
X    int worth = 0, u2;
X
X    for_all_unit_types(u2) {
X	if (could_capture(u2, e)) {
X	    worth += utypes[u].capacity[u2] * utypes[u2].capture[e];
X	}
X    }
X    worth *= utypes[u].speed;
X    return worth;
X}
X
X/* At the beginning of each turn, review situation, and maybe make some */
X/* new plans. */
X
Xinit_machine_turn(side)
XSide *side;
X{
X    if (global.time > 10 || probability(20)) decide_resignation(side);
X    if (!side_plan(side)->shouldresign) {
X	review_groups(side);
X	form_new_groups(side);
X    }
X}
X
X/* Sometimes there is no point in going on, but be careful not to be too */
X/* pessimistic. */
X
Xdecide_resignation(side)
XSide *side;
X{
X    int opposed, own, odds, chance;
X    Side *side1, *side2;
X    Plan *plan = side_plan(side);
X
X    for_all_sides(side1) {
X	plan->allieds[side_number(side1)] = plan->knowns[side_number(side1)];
X	for_all_sides(side2) {
X	    if (side1 != side2 && allied_side(side1, side2)) {
X		plan->allieds[side_number(side1)] += 
X		    plan->knowns[side_number(side2)];
X	    }
X	}
X    }
X    own = plan->allieds[side_number(side)];
X    for_all_sides(side1) {
X	if (enemy_side(side, side1)) {
X	    opposed = plan->allieds[side_number(side1)];
X	    if (own == 0) {
X		if (opposed > 0) plan->shouldresign = TRUE;
X	    } else {
X		odds = (100 * opposed) / own;
X		if (odds > 200) {
X		    chance = odds / 10;
X		    if (probability(chance)) plan->shouldresign = TRUE;
X		}
X	    }
X	}
X    }
X}
X
X/* Review existing groups and get rid of useless ones.  Start by recomputing */
X/* the size, since we don't update when units die or get transferred. */
X
Xreview_groups(side)
XSide *side;
X{
X    int g, view;
X    Plan *plan = side_plan(side);
X    Unit *unit;
X
X    for (g = 1; g < MAXGROUPS; ++g) plan->group[g].size = 0;
X    for_all_units(unit) {
X	if (unit->side == side) plan->group[unit->group].size++;
X    }
X    for (g = 1; g < MAXGROUPS; ++g) {
X	switch (plan->group[g].goal) {
X	case NOGROUP:
X	    /* a non-existent group */
X	    break;
X	case HITTARGET:
X	    view = side_view(side, plan->group[g].x, plan->group[g].y);
X	    if (view == EMPTY || view == UNSEEN ||
X		side_n(vside(view)) == NULL ||
X		allied_side(side, side_n(vside(view)))) {
X		disband_group(side, g);
X	    }
X	    break;
X	case CAPTARGET:
X	    view = side_view(side, plan->group[g].x, plan->group[g].y);
X	    if (view == EMPTY || view == UNSEEN ||
X		allied_side(side, side_n(vside(view)))) {
X		disband_group(side, g);
X	    }
X	    break;
X	case EXPLORE:
X	    view = side_view(side, plan->group[g].x, plan->group[g].y);
X	    if (view != UNSEEN) {
X		disband_group(side, g);
X	    }
X	    break;
X	case DEFEND:
X	    /* should study area to decide about desirabilty of disbanding */
X	    if (probability(3)) disband_group(side, g);
X	    break;
X	case OCCUPYHEX:
X	    /* occupying should only end if no longer a victory condition */
X	    break;
X	default:
X	    case_panic("group goal", plan->group[g].goal);
X	    break;
X	}
X    }
X}
X
X/* Decide about the formation of new groups, but don't recreate copies of */
X/* groups that already exist. */
X
Xform_new_groups(side)
XSide *side;
X{
X    int i, g, x, y, view, etype, x0, x1, x2, y1, y2, choice;
X    int pri, sumx = 0, sumy = 0, n = 0;
X    Plan *plan = side_plan(side);
X    Side *eside;
X
X    if (!world.known) {
X	if (!find_group(side, EXPLORE, -1, -1)) {
X	    x0 = 0 + random(world.width/3);
X	    x1 = world.width/3 + random(world.width/3);
X	    x2 = (2*world.width)/3 + random(world.width/3);
X	    y1 = 0 + random(world.height/2);
X	    y2 = (3*world.height)/4 + random(world.height/2);
X	    form_group(side, EXPLORE, x0, y1, 0);
X	    form_group(side, EXPLORE, x1, y1, 0);
X	    form_group(side, EXPLORE, x2, y1, 0);
X	    form_group(side, EXPLORE, x0, y2, 0);
X	    form_group(side, EXPLORE, x1, y2, 0);
X	    form_group(side, EXPLORE, x2, y2, 0);
X	}
X    }
X    if (global.time < 10 || probability(20)) {
X	for (i = 0; i < MAXSIDES; ++i) plan->knowns[i] = 0;
X	for (y = 0; y < world.height; ++y) {
X	    for (x = 0; x < world.width; ++x) {
X		view = side_view(side, x, y);
X		if (view != EMPTY && view != UNSEEN) {
X		    if (side == side_n(vside(view))) {
X			sumx += x;  sumy += y;  n++;
X		    }
X		}
X	    }
X	}
X	if (n > 0) {
X	    plan->cx = sumx / n;  plan->cy = sumy / n;
X	}
X	for (y = 0; y < world.height; ++y) {
X	    for (x = 0; x < world.width; ++x) {
X		view = side_view(side, x, y);
X		if (view != EMPTY && view != UNSEEN) {
X		    eside = side_n(vside(view));
X		    etype = vtype(view);
X		    if (!allied_side(side, eside)) {
X			choice = (capturable(etype) ? CAPTARGET : HITTARGET);
X			if (!find_group(side, choice, x, y)) {
X			    pri =
X				100 / (distance(x, y, plan->cx, plan->cy) + 1);
X			    if ((g = form_group(side, choice, x, y, pri))) {
X				plan->group[g].etype = etype;
X			    }
X			}
X		    } else {
X			if (!mobile(etype) && !defended(side, x, y)) {
X			    if ((g = form_group(side, DEFEND, x, y, 0))) {
X				plan->group[g].area = 3;
X			    }
X			}
X		    }
X		    if (eside != NULL) 
X			plan->knowns[side_number(eside)] += 
X			    utypes[etype].territory;
X		}
X	    }
X	}
X    }
X    /* form a hex occupation group if hex mentioned in win/lose */
X}
X
X/* Decides if unit has nothing covering it. */
X
Xdefended(side, x, y)
XSide *side;
Xint x, y;
X{
X    int g;
X    Plan *plan = side_plan(side);
X
X    for (g = 1; g < MAXGROUPS; ++g) {
X	if ((plan->group[g].goal == DEFEND) &&
X	    (distance(x, y, plan->group[g].x, plan->group[g].y) <=
X	     plan->group[g].area))
X	    return TRUE;
X    }
X    return FALSE;
X}
X
X/* When forming a group, first pick out an unused group, then bump a lower */
X/* priority group if there's too many.  If it's of lower or equal priority, */
X/* then don't form the group at all (failure on equal priorities reduces */
X/* fickleness). */
X
Xform_group(side, goal, x, y, priority)
XSide *side;
Xint goal, x, y, priority;
X{
X    int g;
X    Plan *plan = side_plan(side);
X
X    for (g = 1; g < MAXGROUPS; ++g) {
X	if (plan->group[g].goal == NOGROUP) break;
X    }
X    if (g == MAXGROUPS) {
X	for (g = 1; g < MAXGROUPS; ++g) {
X	    if (priority > plan->group[g].priority) {
X		disband_group(side, g);
X		break;
X	    }
X	}
X    }
X    if (g < MAXGROUPS) {
X	plan->group[g].goal = goal;
X	plan->group[g].priority = priority;
X	plan->group[g].x = x;
X	plan->group[g].y = y;
X	plan->group[g].etype = NOTHING;
X	plan->group[g].area = 1;
X	plan->group[g].size = 0;
X	if (Debug) printf("%s form group %d with goal %d -> %d,%d\n",
X			  side->name, g, goal, x, y);
X	return g;
X    } else {
X	return 0;
X    }
X}
X
X/* When group's goal accomplished, release the units for other activities. */
X/* Not very efficient to scan all units, but simpler and safer than links. */
X
Xdisband_group(side, g)
XSide *side;
Xint g;
X{
X    Unit *unit;
X    Plan *plan = side_plan(side);
X
X    if (Debug) printf("%s disband group %d with goal %d -> %d,%d\n",
X		      side->name, g, plan->group[g].goal, 
X		      plan->group[g].x, plan->group[g].y);
X    plan->group[g].goal = NOGROUP;
X    plan->group[g].size = 0;
X    for_all_units(unit) {
X	if (unit->side == side && unit->group == g) {
X	    unit->group = NOGROUP;
X	    unit->goal = NOGOAL;
X	}
X    }
X}
X
X/* Given a goal and argument, see if a group already exists like that. */
X
Xfind_group(side, goal, x, y)
XSide *side;
Xint goal, x, y;
X{
X    int g;
X
X    for (g = 1; g < MAXGROUPS; ++g) {
X	if ((side_plan(side)->group[g].goal == goal) &&
X	    (x == -1 || side_plan(side)->group[g].x == x) &&
X	    (y == -1 || side_plan(side)->group[g].y == y))
X	    return g;
X    }
X    return 0;
X}
X
X/* Decide whether a change of product is desirable. */
X
Xchange_machine_product(unit)
XUnit *unit;
X{
X    int u = unit->type;
X
X    if (Freeze) {
X	return FALSE;
X    } else if (utypes[u].maker) {
X	if (producing(unit)) {
X	    if ((unit->built > 5) ||
X		((utypes[u].make[unit->product] * unit->built) > 50)) {
X		return TRUE;
X	    }
X	} else {
X	    return TRUE;
X	}
X    }
X    return FALSE;
X}
X
X/* Machine algorithm for deciding what a unit should build. This routine */
X/* must return the type of unit decided upon.  Variety of production is */
X/* important, as is favoring types which can leave the builder other than */
X/* on a transport.  Capturers of valuable units are also highly preferable. */
X
Xmachine_product(unit)
XUnit *unit;
X{
X    int u = unit->type, type;
X    int i, j, k, d, x, y, value, bestvalue, besttype;
X    int adjterr[MAXTTYPES];
X
X    mside = unit->side;
X    for_all_terrain_types(i) adjterr[i] = 0;
X    for_all_directions(d) {
X	x = wrap(unit->x + dirx[d]);  y = unit->y + diry[d];
X	adjterr[terrain_at(x, y)]++;
X    }
X    besttype = period.firstptype;
X    bestvalue = 0;
X    for_all_unit_types(i) {
X	value = 0;
X	if (!could_make(u, i)) value = -1000;
X	if (mobile(i)) {
X	    for_all_terrain_types(j) {
X		if (could_move(i, j)) value += adjterr[j];
X	    }
X	    if (value <= 0) value = -1000;
X	}
X	for_all_unit_types(j) {
X	    if (could_capture(i, j)) value += 2;
X	    if (could_carry(i, j)) {
X		for_all_unit_types(k) {
X		    if (could_capture(j, k)) value += 1;
X		}
X	    }
X	}
X	if (mside->building[i] == 0) value += period.numutypes / 2;
X	if (mside->building[i] == 1) value += 1;
X	if (mside->units[i] == 0) value += period.numutypes / 4;
X	if (value > bestvalue) {
X	    besttype = i;
X	    bestvalue = value;
X	}
X    }
X    type = besttype;
X    /* safety check */
X    if (!could_make(unit->type, type)) type = NOTHING;
X    if (Debug) printf("%s will now build %s units\n",
X		      unit_handle(NULL, unit), 
X		      (type == NOTHING ? "no" : utypes[type].name));
X    return type;
X}
X
X/* Decide on and make a move or set orders for a machine player. */
X
Xmachine_move(unit)
XUnit *unit;
X{
X    munit = unit;
X    mside = unit->side;
X    if (Freeze) {
X	order_sentry(unit, 1);
X    } else if (humanside(mside)) {
X	unit->goal = DRIFT;
X	if (maybe_return_home(unit)) return;
X	/* need to decide about "short-term" tactics */
X	search_for_best_move(unit);
X    } else if (side_plan(mside)->shouldresign && flip_coin()) {
X	resign_game(mside, NULL);
X    } else {
X	if (unit->group == NOGROUP) decide_group(unit);
X	if (unit->goal == NOGOAL) decide_goal(unit);
X	if (maybe_return_home(unit)) return;
X	if (probability(50) && short_term(unit)) return;
X	search_for_best_move(unit);
X    }
X}
X
X/* Picking the correct units for a group is essential to its success. */
X/* We rate the unit for its suitability for each group (which will also */
X/* be adjusted by the group's needs). */
X
Xdecide_group(unit)
XUnit *unit;
X{
X    int g, t, suitability, best = 0, bestgroup = 0;
X    Plan *plan = side_plan(unit->side);
X
X    for (g = 1; g < MAXGROUPS; ++g) {
X	suitability = 0;
X	switch (plan->group[g].goal) {
X	case NOGROUP:
X	    break;
X	case HITTARGET:
X	    suitability += bhw[unit->type][plan->group[g].etype];
X	    suitability += bthw[unit->type][plan->group[g].etype];
X	    suitability = min(suitability, 10000);
X	    suitability /= (distance(unit->x, unit->y,
X				     plan->group[g].x, plan->group[g].y) + 1);
X	    suitability /= (plan->group[g].size / 10 + 1);
X	    /* should be fast units (?) with good odds against etype */
X	    /* also need some way to decide about adding transports */
X	    break;
X	case CAPTARGET:
X	    suitability += bcw[unit->type][plan->group[g].etype];
X	    suitability += btcw[unit->type][plan->group[g].etype];
X	    suitability = min(suitability, 10000);
X	    suitability /= (distance(unit->x, unit->y,
X				     plan->group[g].x, plan->group[g].y) + 1);
X	    suitability /= (plan->group[g].size / 10 + 1);
X	    /* also need some way to decide about adding transports */
X	    /* and maybe defenders for vulnerable capturers/transports */
X	    /* size must be sufficient to be nearly certain of taking */
X	    break;
X	case EXPLORE:
X	    suitability = 100;
X	    suitability /= (plan->group[g].size + 1);
X	    break;
X	case DEFEND:
X	    suitability = random(100);
X	    break;
X	case OCCUPYHEX:
X	    /* assign a group capable of reaching the hex */
X	    break;
X	default:
X	    case_panic("group goal", plan->group[g].goal);
X	    break;
X	}
X	suitability *= (plan->group[g].priority + 1);
X	if (suitability > best) {
X	    best = suitability;
X	    bestgroup = g;
X	}
X    }
X    unit->group = bestgroup;
X    unit->goal = NOGOAL;
X    plan->group[bestgroup].size++;
X    if (Debug) printf("%s assigned to group %d\n",
X		      unit_handle(NULL, unit), bestgroup);
X}
X
X/* Set up goals for units that need them. */
X/* Goals should differ according to unit's role in group... */
X
Xdecide_goal(unit)
XUnit *unit;
X{
X    Plan *plan = side_plan(unit->side);
X
X    switch (plan->group[unit->group].goal) {
X    case NOGOAL:
X	/* dubious */
X	unit->goal = DRIFT;
X	unit->gx = unit->gy = 0;
X	break;
X    case HITTARGET:
X	unit->goal = ASSAULT;
X	unit->gx = plan->group[unit->group].x;
X	unit->gy = plan->group[unit->group].y;
X	break;
X    case CAPTARGET:
X	if (could_capture(unit->type, plan->group[unit->group].etype)) {
X	    unit->goal = ASSAULT;
X	} else if (probability(fullness(unit))) {
X	    unit->goal = APPROACH;
X	} else {
X	    unit->goal = LOAD;
X	}
X	unit->gx = plan->group[unit->group].x;
X	unit->gy = plan->group[unit->group].y;
X	break;
X    case EXPLORE:
X	unit->goal = APPROACH;
X	unit->gx = plan->group[unit->group].x;
X	unit->gy = plan->group[unit->group].y;
X	break;
X    case DEFEND:
X	unit->goal = DRIFT;
X	unit->gx = plan->group[unit->group].x;
X	unit->gy = plan->group[unit->group].y;
X	break;
X    case OCCUPYHEX:
X	unit->goal = APPROACH;
X	unit->gx = plan->group[unit->group].x;
X	unit->gy = plan->group[unit->group].y;
X	break;
X    default:
X	case_panic("group goal", plan->group[unit->group].goal);
X	break;
X    }
X    if (Debug) printf("%s group %d assigned goal %d -> %d,%d\n",
X		      unit_handle(NULL, unit), unit->group, unit->goal,
X		      unit->gx, unit->gy);
X}
X
X/* See if the location has a unit that can take us in for refueling */
X/* (where's the check for refueling ability?) */
X
Xhaven_p(x, y)
Xint x, y;
X{
X    Unit *unit = unit_at(x, y);
X
X    return ((unit != NULL && mside == unit->side && alive(unit) &&
X	     can_carry(unit, munit) && !might_be_captured(unit)));
X}
X
X/* See if the location has a unit that can repair us */
X
Xshop_p(x, y)
Xint x, y;
X{
X    Unit *unit = unit_at(x, y);
X
X    return (unit != NULL && munit->side == unit->side && alive(unit) &&
X	    can_carry(unit, munit) && 
X	    could_repair(unit->type, munit->type));
X}
X
X/* See if we're in a bad way, either on supply or hits, and get to safety */
X/* if possible.  If not, then move on to other actions. */
X/* Can't be 100% though, there might be some problem preventing move */
X
Xmaybe_return_home(unit)
XUnit *unit;
X{
X    int ux = unit->x, uy = unit->y, ox, oy, range;
X
X    if (low_supplies(unit) && probability(98)) {
X	range = range_left(unit);
X	if (unit->transport) {
X	    order_sentry(unit, 1);
X	    return TRUE;
X	} else if (!unit->occupant) {
X	    if (Debug) printf("%s should return\n", unit_handle(NULL, unit));
X	    if ((range * range < numunits) ?
X		(search_area(ux, uy, range, haven_p, &ox, &oy)) :
X		(find_closest_unit(ux, uy, range, haven_p, &ox, &oy))) {
X		order_moveto(unit, ox, oy);
X		unit->orders.flags |= SHORTESTPATH;
X		unit->orders.flags &=
X		    ~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
X		if (Debug) printf("    will resupply\n");
X		return TRUE;
X	    }
X	} else {
X	    return FALSE;  /* should be more detailed */
X	}
X    }
X    if (cripple(unit) && probability(98)) {
X	if (unit->transport) {
X	    order_sentry(unit, 1);
X	    return TRUE;
X	} else {
X	    if (Debug) printf("%s should repair\n", unit_handle(NULL, unit));
X	    range = range_left(unit);
X	    if ((range * range < numunits) ?
X		(search_area(ux, uy, range, haven_p, &ox, &oy)) :
X		(find_closest_unit(ux, uy, range, shop_p, &ox, &oy))) {
X		order_moveto(unit, ox, oy);
X		unit->orders.flags &= ~SHORTESTPATH;
X		unit->orders.flags &=
X		    ~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
X		if (Debug) printf("    will repair\n");
X		return TRUE;
X	    } else {
X		return FALSE;
X	    }
X	}
X    }
X    if (out_of_ammo(unit) >= 0 && probability(80)) {
X	if (unit->transport) {
X	    order_sentry(unit, 1);
X	    return TRUE;
X	} else {
X	    range = range_left(unit);
X	    if (Debug) printf("%s should reload\n", unit_handle(NULL, unit));
X	    if ((range * range < numunits) ?
X		(search_area(ux, uy, range, haven_p, &ox, &oy)) :
X		(find_closest_unit(ux, uy, range, haven_p, &ox, &oy))) {
X		order_moveto(unit, ox, oy);
X		unit->orders.flags &= ~SHORTESTPATH;
X		unit->orders.flags &= 
X		    ~(ENEMYWAKE|NEUTRALWAKE|SUPPLYWAKE|ATTACKUNIT);
X		if (Debug) printf("    will reload\n");
X		return TRUE;
X	    } else {
X		return FALSE;
X	    }
X	}
X    }
X    return FALSE;
X}
X
X/* Return the distance that we can go by shortest path before running out */
X/* of important supplies.  Will return at least 1, since we can *always* */
X/* move one hex to safety.  This is a worst-case routine, too complicated */
X/* to worry about units getting refreshed by terrain or whatever. */
X
Xrange_left(unit)
XUnit *unit;
X{
X    int u = unit->type, r, least = 12345;
X
X    for_all_resource_types(r) {
X	if (utypes[u].tomove[r] > 0) least = min(least, unit->supply[r]);
X	if (utypes[u].consume[r] > 0)
X	    least = min(least, unit->supply[r] / utypes[u].consume[r]);
X    }
X    return (least == 12345 ? 1 : least);
X}
X
X/* Do short-range planning. */
X
Xshort_term(unit)
XUnit *unit;
X{
X    switch (unit->goal) {
X    case DRIFT:
X    case LOAD:
X    case APPROACH:
X    case ASSAULT:
X	break;
X    default:
X        case_panic("unit goal", munit->goal);
X	break;
X    }
X    return FALSE;
X}
X
X/* Given a position nearby the unit, evaluate it with respect to goals, */
X/* general characteristics, and so forth.  -10000 is very bad, 0 is OK, */
X/* 10000 or so is best possible. */
X
X/* Should downrate hexes within reach of enemy retaliation. */
X/* Should downrate hexes requiring supply consumption to enter/occupy. */
X
Xevaluate_hex(x, y)
Xint x, y;
X{
X    bool adjhex, ownhex;
X    int view, etype, dist, worth = 0;
X    int terr = terrain_at(x, y);
X    Side *es;
X    Unit *eunit;
X
X    view = side_view(mside, x, y);
X    dist = distance(munit->x, munit->y, x, y);
X    adjhex = (dist == 1);
X    ownhex = (dist == 0);
X
X    if (y <= 0 || y >= world.height-1) {
X	worth = -10000;
X    } else {
X	switch (munit->goal) {
X	case DRIFT:
X	    if (ownhex) {
X		worth = -1;
X	    } else if (view == UNSEEN) {
X		worth = random(100) / dist;
X	    } else if (view == EMPTY) {
X		worth = -100;
X		if (impassable(munit, x, y)) worth -= 900;
X	    } else {
X		es = side_n(vside(view));
X		etype = vtype(view);
X		if (es == NULL) {
X		    if (could_capture(munit->type, etype)) {
X			worth = 20000 / dist;
X		    } else {
X			worth = -10000;
X		    }
X		} else if (!allied_side(mside, es)) {
X		    worth = 200 + attack_worth(munit, etype);
X		    worth += threat(mside, etype, x, y);
X		    worth /= dist;
X		} else {
X		}
X	    }
X	    break;
X	case LOAD:
X	    if (ownhex || view == UNSEEN || view == EMPTY) {
X		worth = -1;
X	    } else {
X		es = side_n(vside(view));
X		if (mside == es) {
X		    if ((eunit = unit_at(x, y)) != NULL) {
X			if (eunit->group == munit->group) {
X			    worth = 4000;
X			    worth /= dist;
X			}
X		    }
X		} else {
X		    worth = -100;
X		}
X	    }
X	    break;
X	case APPROACH:
X	case ASSAULT:
X	    if (ownhex) {
X		worth = -100;
X	    } else if (view == UNSEEN) {
X	    } else if (view == EMPTY) {
X		if (impassable(munit, x, y)) worth -= 900;
X	    } else if (x == munit->gx && y == munit->gy) {
X		worth = 10000;
X	    } else {
X		es = side_n(vside(view));
X		etype = vtype(view);
X		if (es == NULL) {
X		    if (could_capture(munit->type, etype)) {
X			worth = 20000 / dist;
X		    } else {
X			worth = -10000;
X		    }
X		} else if (!allied_side(mside, es)) {
X		    worth = 200 + attack_worth(munit, etype);
X		    worth += threat(mside, etype, x, y);
X		    worth /= dist;
X		} else {
X		    es = side_n(vside(view));
X		    if (mside == es) {
X			if ((eunit = unit_at(x, y)) != NULL) {
X			    if (eunit->group == munit->group &&
X				eunit->goal == LOAD &&
X				could_carry(eunit->type, munit->type)) {
X				worth = 4000;
X				worth /= dist;
X			    }
X			}
X		    } else {
X			worth = -100;
X		    }
X		}
X	    }
X	    break;
X	default:
X	    case_panic("unit goal", munit->goal);
X	    break;
X	}
X    }
X    if ((munit->gx > 0 || munit->gy > 0) &&
X	(distance(x, y, munit->gx, munit->gy) <
X	 distance(munit->x, munit->y, munit->gx, munit->gy))) {
X	worth += 1000;
X    }
X    worth -= 100;
X    worth += utypes[munit->type].productivity[terr];
X    aset(localworth, x, y, worth);
X}
X
X/* Scan evaluated area looking for best overall hex. */
X
Xint bestworth = -10000, bestx, besty;
X
Xmaximize_worth(x, y)
Xint x, y;
X{
X    int worth;
X
X    worth = aref(localworth, x, y);
X    if (worth >= 0) {
X	if (worth > bestworth) {
X	    bestworth = worth;  bestx = x;  besty = y;
X	} else if (worth == bestworth && flip_coin()) {
X	    bestworth = worth;  bestx = x;  besty = y;
X	}
X    }
X}
X
X/* Search for most favorable odds anywhere in the area, but only for */
X/* the remaining moves in this turn.  Multi-turn tactics is elsewhere. */
X
X/* Need to be able to direct unit to sit at best hex if not at max move. */
X
X/* Could compare value of hexes to value of goal here and go for goal maybe? */
X
Xsearch_for_best_move(unit)
XUnit *unit;
X{
X    int ux = unit->x, uy = unit->y, range = unit->movesleft;
X
X    if (!mobile(unit->type)) {
X	order_sentry(unit, 100);
X	return;
X    }
X    if (Debug) printf("%s ", unit_handle(NULL, unit));
X    bestworth = -20000;
X    apply_to_area(ux, uy, range, evaluate_hex);
X    apply_to_area(ux, uy, range, maximize_worth);
X    if (bestworth >= 5000) {
X	if (unit->transport != NULL && mobile(unit->transport->type)) {
X	    if (Debug) printf("sleeping on transport\n");
X	    order_sentry(unit, 5);
X	} else if ((ux == bestx && uy == besty) || !can_move(unit)) {
X	    if (Debug) printf("staying put\n");
X	    order_sentry(unit, 1);
X	} else if (probability(90)) {
X	    if (Debug) printf("moving to %d,%d (worth %d)\n",
X			      bestworth, bestx, besty);
X	    order_moveto(unit, bestx, besty);
X	    unit->orders.flags &= ~SHORTESTPATH;
X	} else {
X	    if (Debug) printf("hanging around\n");
X	    order_sentry(unit, random(5));
X	}
X    } else if (unit->goal != DRIFT && probability(90)) {
X	switch (unit->goal) {
X	case DRIFT:
X	    break;
X	case LOAD:
X	    if (unit->occupant != NULL) {
X		if (Debug) printf("starting off to goal\n");
X		unit->goal = APPROACH;
X		order_moveto(unit, unit->gx, unit->gy);
X	    } else {
X		if (bestworth >= 0) {
X		    if (Debug) printf("loading at %d,%d (worth %d)\n",
X				      bestworth, bestx, besty);
X		    order_moveto(unit, bestx, besty);
X		    unit->orders.flags &= ~SHORTESTPATH;
X		} else {
X		    if (Debug) printf("moving slowly about\n");
X		    order_movedir(unit, random_dir(), 1);
X		}
X	    }
X	    break;
X	case APPROACH:
X	case ASSAULT:
X	    if (unit->transport != NULL) {
X		if (unit->transport->group == unit->group) {
X		    if (Debug) printf("riding in transport\n");
X		    order_sentry(unit, 4);
X		} else if (!can_move(unit)) {
X		    if (Debug) printf("waiting to get off\n");
X		    order_sentry(unit, 2);
X		} else {
X		    if (Debug) printf("leaving for %d,%d\n",
X				      unit->gx, unit->gy);
X		    order_moveto(unit, unit->gx, unit->gy);
X		}
X	    } else {
X		if (Debug) printf("approaching %d,%d\n", unit->gx, unit->gy);
X		order_moveto(unit, unit->gx, unit->gy);
X	    }
X	    break;
X	default:
X            case_panic("unit goal", munit->goal);
X	    break;
X	}
X    } else {
X	if (can_produce(unit) && unit->transport == NULL && probability(90)) {
X	    if (Debug) printf("going to build something\n");
X	    set_product(unit, machine_product(unit));
X	    set_schedule(unit);
X	    order_sentry(unit, unit->schedule+1);
X	} else if (probability(90)) {
X	    if (Debug) printf("going in random direction\n");
X	    order_movedir(unit, random_dir(), random(3)+1);
X	} else {
X	    if (Debug) printf("hanging around\n");
X	    order_sentry(unit, random(4)+1);
X	}
X    }
X}
X
X/* This is a heuristic estimation of the value of one unit type hitting */
X/* on another.  Should take cost of production into account as well as the */
X/* chance and significance of any effect. */
X
Xattack_worth(unit, etype)
XUnit *unit;
Xint etype;
X{
X    int utype = unit->type, worth;
X
X    worth = bhw[utype][etype];
X    if (utypes[utype].damage[etype] >= utypes[etype].hp)
X	worth *= 2;
X    if (utypes[etype].damage[utype] >= unit->hp)
X	worth /= (could_capture(utype, etype) ? 1 : 4);
X    if (could_capture(utype, etype)) worth *= 4;
X    return worth;
X}
X
X
X/* Support functions. */
X
X/* True if unit is in immediate danger of being captured. */
X/* Needs check on capturer transport being seen. */
X
Xmight_be_captured(unit)
XUnit *unit;
X{
X    int d, x, y;
X    Unit *unit2;
X
X    for_all_directions(d) {
X	x = wrap(unit->x + dirx[d]);  y = unit->y + diry[d];
X	if (((unit2 = unit_at(x, y)) != NULL) &&
X	    (enemy_side(unit->side, unit2->side)) &&
X	    (could_capture(unit2->type, unit->type))) return TRUE;
X    }
X    return FALSE;
X}
X
X/* Return true if the given unit type at given position is threatened. */
X
Xthreat(side, u, x0, y0)
XSide *side;
Xint u, x0, y0;
X{
X    int d, x, y, view, thr = 0;
X    Side *side2;
X
X    for_all_directions(d) {
X	x = wrap(x0 + dirx[d]);  y = y0 + diry[d];
X	view = side_view(side, x, y);
X	if (view != UNSEEN && view != EMPTY) {
X	    side2 = side_n(vside(view));
X	    if (allied_side(side, side2)) {
X		if (could_capture(u, vtype(view))) thr += 1000;
X		if (bhw[u][vtype(view)] > 0) thr += 100;
X	    }
X	}
X    }
X    return thr;
X}
X
X/* Test if unit can move out into adjacent hexes. */
X
Xcan_move(unit)
XUnit *unit;
X{
X    int d, x, y;
X
X    for_all_directions(d) {
X	x = wrap(unit->x + dirx[d]);  y = limit(unit->y + diry[d]);
X	if (could_move(unit->type, terrain_at(x, y))) return TRUE;
X    }
X    return FALSE;
X}
X
X/* Returns the type of missing supplies. */
X
Xout_of_ammo(unit)
XUnit *unit;
X{
X    int u = unit->type, r;
X
X    for_all_resource_types(r) {
X	if (utypes[u].hitswith[r] > 0 && unit->supply[r] <= 0)
X	    return r;
X    }
X    return (-1);
X}
X
X/* Returns TRUE if type is capturable somehow. */
X
Xcapturable(e)
Xint e;
X{
X    int u;
X
X    if (utypes[e].surrender > 0 || utypes[e].siege > 0) return TRUE;
X    for_all_unit_types(u) if (could_capture(u, e)) return TRUE;
X    return FALSE;
X}
X
X/* True if the given unit is a sort that can build other units. */
X
Xcan_produce(unit)
XUnit *unit;
X{
X    int p;
X
X    for_all_unit_types(p) {
X	if (could_make(unit->type, p)) return TRUE;
X    }
X    return FALSE;
X}
X
X/* Return percentage of capacity. */
X
Xfullness(unit)
XUnit *unit;
X{
X    int u = unit->type, o, cap = 0, num = 0, vol = 0;
X    Unit *occ;
X
X    for_all_unit_types(o) cap += utypes[u].capacity[o];
X    for_all_occupants(unit, occ) {
X	num++;
X	vol += utypes[occ->type].volume;
X    }
X    if (utypes[u].holdvolume > 0) {
X	return ((100 * vol) / utypes[u].holdvolume);
X    } else if (cap > 0) {
X	return ((100 * num) / cap);
X    } else {
X	fprintf(stderr, "Fullness ???\n");
X    }
X}
X
Xfind_closest_unit(x0, y0, maxdist, pred, rxp, ryp)
Xint x0, y0, maxdist, (*pred)(), *rxp, *ryp;
X{
X    Unit *unit;
X
X    for_all_units(unit) {
X	if (distance(x0, y0, unit->x, unit->y) <= maxdist) {
X	    if ((*pred)(unit->x, unit->y)) {
X		*rxp = unit->x;  *ryp = unit->y;
X		return TRUE;
X	    }
X	}
X    }
X    return FALSE;
X}
END_OF_mplay.c
if test 32778 -ne `wc -c <mplay.c`; then
    echo shar: \"mplay.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of archive 3 \(of 18\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 18 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0