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