Compare commits
15 commits
master
...
pathfind-t
Author | SHA1 | Date | |
---|---|---|---|
b5b3a7b1ef | |||
78d7db3baf | |||
ba08bfc0c9 | |||
b340b2d194 | |||
70ef6066f2 | |||
e9307b7b1d | |||
d8bef8e806 | |||
fc0c658646 | |||
e882567889 | |||
7edcd3ea77 | |||
a02d3e9fc1 | |||
0385c67a8f | |||
2797e58c20 | |||
8f92fe40f4 | |||
c2628d43b7 |
20 changed files with 2442 additions and 44 deletions
3
Make.mk
3
Make.mk
|
@ -119,7 +119,7 @@ obj := $(csrc:.c=.o) $(filter %.o, $(ac:.c=.o))
|
|||
# Dependencies:
|
||||
deps := $(obj:.o=.d)
|
||||
# Library archives:
|
||||
libs := $(addprefix lib/, libcommon.a libgen.a libglobal.a)
|
||||
libs := $(addprefix lib/, libcommon.a libas.a libgen.a libglobal.a)
|
||||
# Programs:
|
||||
util := $(addprefix src/util/, $(addsuffix $(EXEEXT), empdump empsched fairland files pconfig))
|
||||
client := src/client/empire$(EXEEXT)
|
||||
|
@ -291,6 +291,7 @@ $(client): $(filter src/client/%, $(obj)) src/lib/global/version.o
|
|||
|
||||
$(util): $(libs)
|
||||
|
||||
lib/libas.a: $(filter src/lib/as/%, $(obj))
|
||||
lib/libcommon.a: $(filter src/lib/common/%, $(obj))
|
||||
lib/libgen.a: $(filter src/lib/gen/%, $(obj))
|
||||
lib/libglobal.a: $(filter src/lib/global/%, $(obj))
|
||||
|
|
|
@ -67,6 +67,9 @@ extern int diroff[DIR_MAP+1][2];
|
|||
extern char dirch[DIR_MAP+2];
|
||||
extern char *routech[DIR_LAST+1];
|
||||
|
||||
/* src/lib/common/bestpath.c */
|
||||
extern char *bestownedpath(char *, char *, int, int, int, int, int);
|
||||
|
||||
/* src/lib/common/findpath.c */
|
||||
extern void path_find_from(coord, coord, natid, int);
|
||||
extern double path_find_to(coord, coord);
|
||||
|
@ -82,12 +85,16 @@ extern void path_find_print_stats(void);
|
|||
#endif
|
||||
|
||||
/* src/lib/common/path.c */
|
||||
extern void bp_enable_cachepath(void);
|
||||
extern void bp_disable_cachepath(void);
|
||||
extern void bp_clear_cachepath(void);
|
||||
extern char *BestDistPath(char *, struct sctstr *, struct sctstr *,
|
||||
double *);
|
||||
extern char *BestLandPath(char *, struct sctstr *, struct sctstr *,
|
||||
double *, int);
|
||||
extern char *BestShipPath(char *, int, int, int, int, int);
|
||||
extern char *BestAirPath(char *, int, int, int, int);
|
||||
extern double pathcost(struct sctstr *, char *, int);
|
||||
|
||||
/* src/lib/subs/paths.c */
|
||||
extern char *getpath(char *, char *, coord, coord, int, int, enum p_mode);
|
||||
|
|
|
@ -253,6 +253,8 @@ int zdon(void);
|
|||
/*
|
||||
* src/lib/common/ *.c
|
||||
*/
|
||||
/* bestpath.c */
|
||||
/* in path.h */
|
||||
/* conftab.c */
|
||||
extern int read_builtin_tables(void);
|
||||
extern int read_custom_tables(void);
|
||||
|
|
41
src/lib/as/COPYRIGHT
Normal file
41
src/lib/as/COPYRIGHT
Normal file
|
@ -0,0 +1,41 @@
|
|||
(Note that this copyright notice was changed with permission from Phil
|
||||
Lapsley to a copyright that complies with the GNU GPL. The new
|
||||
copyright is supplied here, along with the old copyright notice
|
||||
below.)
|
||||
|
||||
-----
|
||||
|
||||
A* Search - A search library used in Empire to determine paths between
|
||||
objects.
|
||||
Copyright (C) 1990-1998 Phil Lapsley
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
-----
|
||||
|
||||
Old Copyright notice follows:
|
||||
|
||||
"Copyright 1990 Phil Lapsley. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms for noncommercial
|
||||
purposes in support of BSD Empire are permitted provided that this
|
||||
notice is preserved and that due credit is given to the copyright
|
||||
holder. This software is provided ``as is'' without express or implied
|
||||
warranty. Entities interested in other distribution of this software
|
||||
should contact the copyright holder.
|
||||
|
||||
(Phil is located at phil@east.berkeley.edu)"
|
||||
|
||||
-----
|
300
src/lib/as/README
Normal file
300
src/lib/as/README
Normal file
|
@ -0,0 +1,300 @@
|
|||
Tue Nov 13 11:50:24 PST 1990 Phil Lapsley phil@Berkeley.EDU
|
||||
|
||||
This library implements a reasonably general version of the A* algorithm.
|
||||
Basically, A* is like an ordered search, but with a heuristic that allows
|
||||
it to make a better choices as to which path to take. The subdirectory
|
||||
"test" has example code for using A* to search a weighted cartesian matrix.
|
||||
The file "XXX//bestpath.c" has code to interface Empire to the A*
|
||||
algorithms.
|
||||
|
||||
This library is copyrighted; see the file COPYRIGHT for details.
|
||||
|
||||
COMPILATION
|
||||
|
||||
Do a "make" in this directory to make the library. Cd into "test" and
|
||||
do a "make" there to make a test program, called (ta da) "test".
|
||||
|
||||
LIBRARY USAGE
|
||||
|
||||
Pretty much all the data that the user needs to communicate to the library
|
||||
is stored in an "as_data" structure, which is created by a call to "as_init":
|
||||
|
||||
struct as_data *adp;
|
||||
|
||||
adp = as_init( ... );
|
||||
|
||||
The arguments to as_init specify a number of key things the algorithm will
|
||||
use, and these are discussed below.
|
||||
|
||||
Once you have an "as_data" structure, you can fill in its "from" and "to"
|
||||
members, which specify the coordinates of the points between which you want
|
||||
to go. The basic coordinate data structure is the "as_coord", which is
|
||||
just an "x" integer and a "y" integer. So:
|
||||
|
||||
adp->from.x = ...;
|
||||
adp->from.y = ...;
|
||||
adp->to.x = ...;
|
||||
adp->to.y = ...;
|
||||
|
||||
and then call as_search:
|
||||
|
||||
as_search(adp);
|
||||
|
||||
If the return value of as_search is 0, the algorithm found a path from
|
||||
"from" to "to". The path is stored in the "path" member of the "as_data"
|
||||
structure, and is just a linked list of coordinates ("as_coord" structs)
|
||||
from "from" to "to".
|
||||
|
||||
If the return value of as_search is -1, the algorithm couldn't find a
|
||||
path from "from" to "to". If the return is -2, a system error occurred
|
||||
(most probably malloc failed).
|
||||
|
||||
After a call to as_search, lots of malloced data 'n' stuff will be
|
||||
floating around, all pointed to by items in the "as_data" data structure.
|
||||
If you call "as_search" again (presumably with new "from" and "to"
|
||||
coordinates), the system will free these data structures and reallocate
|
||||
new ones. If you no longer want the data structures at all (i.e., you
|
||||
never intend to call "as_search" again), you can call:
|
||||
|
||||
as_delete(adp);
|
||||
|
||||
and this will free up all data associated with the as_data structure pointed
|
||||
to by adp.
|
||||
|
||||
ARGUMENTS TO AS_INIT
|
||||
|
||||
"as_init" takes eight arguments, in the following order:
|
||||
|
||||
maxneighbors The maximum number of neighboring sectors a
|
||||
coordinate can have. On a cartesian grid,
|
||||
this would be 8. On a hex map, it would be 6.
|
||||
|
||||
hashsize The size of the hash table used to determine
|
||||
if we've visited a particular coordinate.
|
||||
|
||||
hashfunc A pointer to a function that takes an
|
||||
"as_coord" as an argument and returns an
|
||||
integer that will be used as a hash value.
|
||||
If this is a NULL pointer, the as library
|
||||
will assign its own hash function that just
|
||||
adds the x and y coordinates together.
|
||||
|
||||
neighborfunc A pointer to a function that takes a coordinate
|
||||
(call it "c"), a pointer to an array of
|
||||
coordinates of length "maxneighbors", and
|
||||
a user data pointer (see below). This
|
||||
function should figure out the coordinates
|
||||
of all the neighbors of "c" and put them in
|
||||
the array cp[0] ... cp[maxneighbors-1].
|
||||
It should then return the number of neighbors
|
||||
found.
|
||||
|
||||
lbcostfunc A pointer to a function that takes two
|
||||
coordinates, call them "from" and "to",
|
||||
and a user data pointer (see below). It
|
||||
returns a double precision *LOWER BOUND*
|
||||
on the cost to get from "from" to "to".
|
||||
"from" and "to" may be separated by an
|
||||
arbitrary distance. More on this below.
|
||||
|
||||
realcostfunc A pointer to a function that takes two
|
||||
coordinates, call them "from" and "to",
|
||||
and a user data pointer (see below). It
|
||||
returns a double precision value of
|
||||
the *actual cost* to move from "from" to
|
||||
"to". Note that "from" will never be more
|
||||
than one sector away from "to".
|
||||
|
||||
seccostfunc A pointer to a function that takes two
|
||||
coordinates, call them "from" and "to",
|
||||
and a user data pointer (see below). It
|
||||
returns a double precision value that will
|
||||
be used to break ties when two nodes have
|
||||
the same lower bound to the target. An
|
||||
example will be discussed below.
|
||||
|
||||
userdata A (char *) that can be a pointer to
|
||||
any kind of data you want. This will
|
||||
be passed as the third argument to the
|
||||
neighborfunc, lbcostfunc, realcostfunc,
|
||||
and seccostfunc.
|
||||
|
||||
Look in test/cart.c to see examples of these functions for cartesian
|
||||
coordinates.
|
||||
|
||||
NOTES ON THE LOWER BOUND FUNCTION
|
||||
|
||||
"lbcostfunc" is *CRUCIAL* to the algorithm's performance. The entire
|
||||
idea behind the A* algorithm is that, when considering whether to move
|
||||
to a new coordinate, you know two things:
|
||||
|
||||
(1) how much it's cost you to get to that coordinate so far,
|
||||
|
||||
(2) a LOWER BOUND on how much it will cost to get to the
|
||||
destination from that coordinate.
|
||||
|
||||
If these two conditions are met, the algorithm will return the optimal
|
||||
path to the destination. The closer the lower bound is to the actual
|
||||
cost to get from one point to another, the quicker the algorithm will
|
||||
find this path. HOWEVER, if the lower bound is ever violated, i.e.,
|
||||
if the so-called lower bound function returns a value that is greater
|
||||
than the actual cost to get to the destination, then the algorithm will
|
||||
not necessarily find the optimal path.
|
||||
|
||||
Example:
|
||||
|
||||
Assume that we're on a cartesian matrix, and the cost to move from one point
|
||||
to another is just the distance between the two points, and that no
|
||||
other costs are involved. In this case, the lower bound function could
|
||||
be the same as the actual cost function, i.e.,
|
||||
|
||||
real cost = lower bound cost = sqrt(dx^2 + dy^2);
|
||||
|
||||
In this case, the algorithm will find the destination as quickly as possible.
|
||||
|
||||
Another example:
|
||||
|
||||
Again assume we're on a cartesian matrix, and the cost to move from
|
||||
one point to another is two things: (1) the distance between them, (2) some
|
||||
arbitrary cost function we get off of a map. E.g.,
|
||||
|
||||
X
|
||||
|
||||
0 1 2 3
|
||||
0 0 0 0 0
|
||||
1 0 0 0 0
|
||||
Y 2 0 0 2 0
|
||||
3 0 0 0 0
|
||||
|
||||
The real cost to move from (x,y) 0,0 to 1,0 is 1. That is, it's the distance
|
||||
(1) plus the value of the map at (1,0), which is 0. The real cost to move
|
||||
from (1,2) to (2,2) is 3: the distance (1) plus the map value at (2,2), which
|
||||
is 2, totaling 3.
|
||||
|
||||
In this case, the lower bound function could still be the distance between
|
||||
the two points, since this WILL NEVER BE MORE than the actual cost, and
|
||||
hence is a lower bound. I.e.,
|
||||
|
||||
real cost = sqrt(dx^2 + dy^2) + map costs
|
||||
|
||||
lower bound cost = sqrt(dx^2 + dy^2)
|
||||
|
||||
lower bound cost <= real cost for all coordinates
|
||||
|
||||
This is what the the example in the "test" directory uses.
|
||||
|
||||
A third example:
|
||||
|
||||
You could make the lower bound function always return 0. This would be
|
||||
a valid lower bound of the cost to move between any two points. In this
|
||||
case, the A* algorithm will behave like a breadth-first search, and isn't
|
||||
very much fun.
|
||||
|
||||
SECONDARY COST FUNCTION
|
||||
|
||||
The algorithm tries new coordinates in order of lowest lower-bound.
|
||||
There can be cases where the lower-bound function for two (or more)
|
||||
coordinates is the same, i.e., there is a tie. The algorithm uses
|
||||
the secondary cost function (which does NOT have to be a lower bound)
|
||||
to choose which coordinate to pick. A typical heuristic might be
|
||||
to use Euclidian distance for this secondary cost function, on the
|
||||
assumption that it's always better to move closer the destination.
|
||||
The Empire code does just this.
|
||||
|
||||
If you don't need a secondary cost function, just specify a NULL pointer
|
||||
to the seccost argument to as_init, and the routines will use 0.0 for you.
|
||||
|
||||
EMPIRE INTERFACE
|
||||
|
||||
The interface code in "XXX/bestpath.c" is a rather complicated example
|
||||
of A* interface code. What it does is based on some features of Empire
|
||||
that are explained here.
|
||||
|
||||
First, movement cost in Empire is based on a quantity called "mobility
|
||||
cost", which is a property of every sector. As we trace a path from
|
||||
source to destination, we add up mobility cost as we go. Once we're
|
||||
there, we have a total mobility cost. This is what we'd like to
|
||||
minimize.
|
||||
|
||||
Second, Empire has highways, which are zero cost movement paths. This
|
||||
hurts the A* algorithm, because it means that the lower bound cost
|
||||
function is very weak. For example, think what happens if we move from
|
||||
one side of the country to another: if the two sectors are attached via
|
||||
a highway, the cost will be very small -- in fact, it will be the cost
|
||||
to move out of the source sector, and the cost to move into the
|
||||
destination sector. If, on the other hand, the two sectors aren't
|
||||
connected by a highway, the cost could be quite large. Thus, the lower
|
||||
bound is just the cost to move out of a sector plus the cost to move
|
||||
into a sector. This is a pretty weak lower bound, and means that we
|
||||
have to explore a lot of sectors.
|
||||
|
||||
Third, the mobility costs tend to tie a lot, as explained in the
|
||||
section above on secondary cost functions. Thus, we use the Empire
|
||||
"mapdist" function as a secondary sort function to break ties. For
|
||||
example, consider the case where a sector borders two highway sectors,
|
||||
one on each side. The lower bound function will say that the two have
|
||||
equal lower bound costs, since they're both highways and cost nothing
|
||||
to move on. The secondary sort function is then used to break the tie --
|
||||
it says, "Take the one that moves you closer to the destination".
|
||||
|
||||
Fourth, all of the information we need about a sector (its mobility
|
||||
cost, who owns it, etc.) is stored in the sector file on disk. This
|
||||
means that the getsect() function to get it off disk will do a read(),
|
||||
which is VERY expensive. Because of the weak lower bound, A* ends up
|
||||
checking lots of sectors, including sectors that it's seen before.
|
||||
To avoid doing thousands of system calls per second, the bestpath.c file
|
||||
has sector caching routines that store in memory copies of every
|
||||
sector we read. (The function bp_getsect handles this). This means
|
||||
we never read a sector from disk more than once, although at the expense
|
||||
of using lots of memory.
|
||||
|
||||
THEORY OF OPERATION
|
||||
|
||||
The basic idea is as follows:
|
||||
|
||||
1. Add the start node to the head of a master queue.
|
||||
2. Is the head of the queue where we want to be? If so, stop.
|
||||
3. Make a list of all the neighbors coordinates around the
|
||||
coordinate of the head.
|
||||
|
||||
4. For each neighbor coordinate,
|
||||
|
||||
Compute the lower bound cost to the destination from here.
|
||||
|
||||
If this coordinate is already on the either the
|
||||
master queue or the "tried" queue:
|
||||
|
||||
4a. If it was on either the "master" queue or the
|
||||
"tried" queue and the one on the queue has a
|
||||
smaller lower-bound than the neighbor, ignore
|
||||
this neighbor and go on to the next neighbor.
|
||||
|
||||
4b. If it was on the "master" queue and the new
|
||||
neighbor has a smaller lower bound,
|
||||
Move the one on the queue to a different
|
||||
queue called "subsumed".
|
||||
|
||||
4c. If it was on the "tried" queue and the new
|
||||
neighbor has a smaller lower bound,
|
||||
Move the one on the "tried" queue to the
|
||||
master queue, and update its lower bound
|
||||
value to be as the new neighbor, and
|
||||
update its backpointer appropriately.
|
||||
|
||||
We now have a list of all neighbor coordinates that are either
|
||||
not on the queue already, or are cheaper than the ones on the
|
||||
queue.
|
||||
|
||||
5. Move the node at the head of the queue (the one whose neighbors
|
||||
we now have in a list) onto a different queue called "tried".
|
||||
|
||||
6. Sort this list of neighbors, and merge it into the master queue,
|
||||
keeping the master queue ordered by lower bound cost to destination.
|
||||
|
||||
7. Goto 2.
|
||||
|
||||
My algorithm does all of this, plus a little more (the above doesn't really
|
||||
mention backpointers except in passing), EXCEPT: I don't do step 4c.
|
||||
I'm not convinced that this can ever occur if the lower bound rule isn't
|
||||
broken, and I have yet to see it occur. However, if as_winnow returns -1,
|
||||
this error has occurred.
|
195
src/lib/as/as.h
Normal file
195
src/lib/as/as.h
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
/*
|
||||
* A* definitions.
|
||||
*
|
||||
* @(#)as.h 1.9 11/13/90
|
||||
*/
|
||||
/*
|
||||
* 11/09/98 - Steve McClure
|
||||
* Added path list caching structures
|
||||
*/
|
||||
|
||||
#ifndef AS_H
|
||||
#define AS_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include "types.h"
|
||||
|
||||
/*
|
||||
* Coordinate.
|
||||
*/
|
||||
struct as_coord {
|
||||
int x, y;
|
||||
};
|
||||
|
||||
/*
|
||||
* Path, made up of a linked list of coordinates.
|
||||
*/
|
||||
struct as_path {
|
||||
struct as_coord c;
|
||||
struct as_path *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* Basic node, used internally by A* algorithm.
|
||||
*/
|
||||
struct as_node {
|
||||
struct as_coord c; /* our coordinate */
|
||||
double knowncost; /* cost so far */
|
||||
double lbcost; /* lower bound on cost to dest */
|
||||
double inclbcost; /* incremental lower bound cost */
|
||||
double seccost; /* used to break ties */
|
||||
int step;
|
||||
int flags;
|
||||
struct as_node *back;
|
||||
};
|
||||
#define AS_TRIED 1 /* we've tried this node before */
|
||||
|
||||
/*
|
||||
* Linked list of nodes, used internally by A* algorithm.
|
||||
*/
|
||||
struct as_queue {
|
||||
struct as_node *np;
|
||||
struct as_queue *next;
|
||||
struct as_queue *prev;
|
||||
};
|
||||
|
||||
/*
|
||||
* Hash table entry, used to determine if we've seen a particular
|
||||
* coordinate before.
|
||||
*/
|
||||
struct as_hash {
|
||||
struct as_coord c;
|
||||
struct as_queue *qp;
|
||||
struct as_hash *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* User's handle on A*, returned by as_init(). Some of the data here is
|
||||
* used by A* internals.
|
||||
*/
|
||||
struct as_data {
|
||||
int maxneighbors; /* max # of neighbors a cell can have */
|
||||
int hashsize; /* size of internal hash table */
|
||||
|
||||
/* hash function (coord -> int) */
|
||||
int (*hash)(struct as_coord);
|
||||
/* function to find neighbors */
|
||||
int (*neighbor)(struct as_coord, struct as_coord *, void *);
|
||||
/* function to give lower bound cost */
|
||||
double (*lbcost)(struct as_coord, struct as_coord, void *);
|
||||
/* function to give real cost */
|
||||
double (*realcost)(struct as_coord, struct as_coord, void *);
|
||||
/* function to secondary cost */
|
||||
double (*seccost)(struct as_coord, struct as_coord, void *);
|
||||
void *userdata; /* user's data, passed to callbacks */
|
||||
struct as_coord from; /* from coordinate */
|
||||
struct as_coord to; /* to coordinate */
|
||||
struct as_path *path; /* solution */
|
||||
|
||||
/* below are "private" to as_ routines */
|
||||
struct as_queue *head;
|
||||
struct as_queue *tried;
|
||||
struct as_hash **hashtab;
|
||||
struct as_queue *subsumed;
|
||||
struct as_coord *neighbor_coords;
|
||||
struct as_node **neighbor_nodes;
|
||||
};
|
||||
|
||||
/*
|
||||
* Added these for caching of paths as we stumble across them
|
||||
*/
|
||||
|
||||
struct as_topath {
|
||||
coord x;
|
||||
struct as_path *path; /* Path from holder of this list to here */
|
||||
struct as_topath *next;
|
||||
};
|
||||
|
||||
struct as_frompath {
|
||||
coord x;
|
||||
struct as_topath **tolist; /* List of nodes we have a path to */
|
||||
struct as_frompath *next;
|
||||
};
|
||||
|
||||
/*
|
||||
* Some cheezy allocation macros.
|
||||
*/
|
||||
#define AS_NEW_ARRAY(p, type, n, err) \
|
||||
(p) = (type *)calloc((n), sizeof(*(p))); \
|
||||
if ((p) == NULL) \
|
||||
return err; \
|
||||
|
||||
#define AS_NEW(p, type, err) \
|
||||
AS_NEW_ARRAY((p), type, 1, err);
|
||||
|
||||
#define AS_NEW_MALLOC(p, type, err) \
|
||||
(p) = (type *)malloc(sizeof(type)); \
|
||||
if ((p) == NULL) \
|
||||
return err; \
|
||||
|
||||
/* Functions that the user can call. */
|
||||
|
||||
extern struct as_data *as_init(int maxneighbors, int hashsize,
|
||||
int (*hashfunc)(struct as_coord),
|
||||
int (*neighborfunc)(struct as_coord,
|
||||
struct as_coord *,
|
||||
void *),
|
||||
double (*lbcostfunc)(struct as_coord,
|
||||
struct as_coord,
|
||||
void *),
|
||||
double (*realcostfunc)(struct as_coord,
|
||||
struct as_coord,
|
||||
void *),
|
||||
double (*seccostfunc)(struct as_coord,
|
||||
struct as_coord,
|
||||
void *),
|
||||
void *userdata);
|
||||
extern int as_search(struct as_data *adp);
|
||||
extern void as_delete(struct as_data *adp);
|
||||
extern void as_reset(struct as_data *adp);
|
||||
extern void as_stats(struct as_data *adp, FILE * fp);
|
||||
extern struct as_path *as_find_cachepath(coord fx, coord fy,
|
||||
coord tx, coord ty);
|
||||
|
||||
/* Functions that are "private" to algorithm */
|
||||
|
||||
extern void as_add_cachepath(struct as_data *adp);
|
||||
extern void as_clear_cachepath(void);
|
||||
extern void as_enable_cachepath(void);
|
||||
extern void as_disable_cachepath(void);
|
||||
|
||||
extern void as_makepath(struct as_data *adp);
|
||||
extern void as_free_path(struct as_path *pp);
|
||||
|
||||
extern int as_costcomp(const void *, const void *);
|
||||
extern struct as_queue *as_extend(struct as_data *adp);
|
||||
extern struct as_queue *as_merge(struct as_data *adp,
|
||||
struct as_queue *head,
|
||||
struct as_node **neighbors);
|
||||
extern struct as_queue *as_iscinq(struct as_data *adp, struct as_coord c);
|
||||
extern void as_setcinq(struct as_data *adp,
|
||||
struct as_coord c, struct as_queue *qp);
|
||||
extern void as_free_hashtab(struct as_data *adp);
|
||||
extern int as_winnow(struct as_data *adp,
|
||||
struct as_coord *coords, int ncoords);
|
||||
|
||||
#endif
|
238
src/lib/as/as_cache.c
Normal file
238
src/lib/as/as_cache.c
Normal file
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Empire - A multi-player, client/server Internet based war game.
|
||||
* Copyright (C) 1986-2010, Dave Pare, Jeff Bailey, Thomas Ruschak,
|
||||
* Ken Stevens, Steve McClure
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* See files README, COPYING and CREDITS in the root of the source
|
||||
* tree for related information and legal notices. It is expected
|
||||
* that future projects/authors will amend these files as needed.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* as_cache.c: Routines used to create/delete caches of A* paths.
|
||||
*
|
||||
* Known contributors to this file:
|
||||
* Steve McClure, 1998
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "as.h"
|
||||
#include "optlist.h"
|
||||
|
||||
/* The way this works is interesting. :) */
|
||||
/* We keep a pointer to a list of pointers. The index into this list
|
||||
* is the y coordinate of the from sector. This member points to a list
|
||||
* of from sectors on that y coordinate. So, we march that list checking
|
||||
* the x value to find the from x,y we want. */
|
||||
/* Once we find the from x,y, that node has the same type of pointer to
|
||||
* a list of pointers. The index into this list is the y coordinate of
|
||||
* the to sector. This member points to a list of to sectors on that y
|
||||
* coordinate. So, we march that list checking the x value to find the
|
||||
* to x,y we want. */
|
||||
/* These lists are dynamically created since the world is dynamically sized. */
|
||||
/* See, I told you it was interesting. :) */
|
||||
|
||||
static struct as_frompath **fromhead = (struct as_frompath **)0;
|
||||
|
||||
/* Note that we only want to cache during updates. Other times, it
|
||||
* probably doesn't make much sense, but can be done. */
|
||||
|
||||
static int as_cachepath_on = 0; /* Default to off */
|
||||
|
||||
#ifdef AS_STATS
|
||||
unsigned as_cache_tries, as_cache_hits;
|
||||
#define as_cache_try() ((void)as_cache_tries++)
|
||||
#define as_cache_hit() ((void)as_cache_hits++)
|
||||
#else
|
||||
#define as_cache_try() ((void)0)
|
||||
#define as_cache_hit() ((void)0)
|
||||
#endif
|
||||
|
||||
void
|
||||
as_enable_cachepath(void)
|
||||
{
|
||||
as_cachepath_on = 1;
|
||||
}
|
||||
|
||||
void
|
||||
as_disable_cachepath(void)
|
||||
{
|
||||
as_cachepath_on = 0;
|
||||
}
|
||||
|
||||
/* Note we want these to be as fast as possible */
|
||||
|
||||
void
|
||||
as_add_cachepath(struct as_data *adp)
|
||||
{
|
||||
struct as_frompath *from;
|
||||
struct as_topath *to = (struct as_topath *)0;
|
||||
struct as_node *np;
|
||||
|
||||
/* Don't do anything if we aren't cacheing these */
|
||||
if (as_cachepath_on == 0)
|
||||
return;
|
||||
|
||||
/* Note we will only allocate this once. Afterwards, we just keep
|
||||
* zeroing it since it's rather small and we don't need to re-allocate
|
||||
* each time. */
|
||||
if (fromhead == NULL) {
|
||||
fromhead = calloc(1, sizeof(struct as_frompath *) * WORLD_Y);
|
||||
if (fromhead == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
np = adp->head->np;
|
||||
for (from = fromhead[adp->from.y]; from; from = from->next)
|
||||
if (from->x == adp->from.x)
|
||||
break;
|
||||
if (from) {
|
||||
for (to = from->tolist[np->c.y]; to; to = to->next) {
|
||||
if (to->x == np->c.x) {
|
||||
/* It is already here! Don't bother adding it again */
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* We must make a new one of these */
|
||||
from = malloc(sizeof(struct as_frompath));
|
||||
if (from == NULL)
|
||||
return;
|
||||
/* And set some stuff */
|
||||
from->x = adp->from.x;
|
||||
/* Here we malloc a whole bunch of tolist pointers. */
|
||||
from->tolist = calloc(1, sizeof(struct as_topath *) * WORLD_Y);
|
||||
/* Now, add from to the global list */
|
||||
from->next = fromhead[adp->from.y];
|
||||
fromhead[adp->from.y] = from;
|
||||
}
|
||||
if (!to) {
|
||||
/* We must make a new one */
|
||||
to = malloc(sizeof(struct as_topath));
|
||||
/* We can't, sorry */
|
||||
if (to == NULL)
|
||||
return;
|
||||
/* Now set some stuff */
|
||||
to->x = np->c.x;
|
||||
/* Now add it to the list we are in */
|
||||
to->next = from->tolist[np->c.y];
|
||||
from->tolist[np->c.y] = to;
|
||||
}
|
||||
/* Now, make the path */
|
||||
as_makepath(adp);
|
||||
/* Now, take the path */
|
||||
to->path = adp->path;
|
||||
/* And clear the path in the adp */
|
||||
adp->path = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
as_clear_cachepath(void)
|
||||
{
|
||||
struct as_frompath *from, *from2;
|
||||
struct as_topath *to, *to2;
|
||||
int i, j;
|
||||
#ifdef AS_STATS
|
||||
size_t index_sz = 0;
|
||||
unsigned index_nb = 0, paths_nb = 0, paths = 0;
|
||||
#define stats_index(sz) ((void)(index_sz += (sz), index_nb++))
|
||||
#else
|
||||
#define stats_index(sz) ((void)0)
|
||||
#endif
|
||||
|
||||
/* Cache not used yet :) */
|
||||
if (fromhead == NULL)
|
||||
return;
|
||||
|
||||
for (j = 0; j < WORLD_Y; j++) {
|
||||
for (from = fromhead[j]; from; from = from2) {
|
||||
for (i = 0; i < WORLD_Y; i++) {
|
||||
for (to = from->tolist[i]; to; to = to2) {
|
||||
to2 = to->next;
|
||||
/* Free this path */
|
||||
#ifdef AS_STATS
|
||||
{
|
||||
struct as_path *pp;
|
||||
for (pp = to->path; pp; pp = pp->next)
|
||||
paths_nb++;
|
||||
paths++;
|
||||
}
|
||||
#endif
|
||||
as_free_path(to->path);
|
||||
/* Free this node */
|
||||
free(to);
|
||||
stats_index(sizeof(*to));
|
||||
}
|
||||
}
|
||||
/* Now, free the list of lists */
|
||||
free(from->tolist);
|
||||
stats_index(WORLD_Y * sizeof(*from->tolist));
|
||||
/* Save the next pointer */
|
||||
from2 = from->next;
|
||||
/* now, free this from node */
|
||||
free(from);
|
||||
stats_index(sizeof(*from));
|
||||
}
|
||||
}
|
||||
/* Note we don't free the fromhead here, we just zero it. That way,
|
||||
we can use it next time without mallocing int */
|
||||
memset(fromhead, 0, (sizeof(struct as_frompath *) * WORLD_Y));
|
||||
stats_index(WORLD_Y * sizeof(*fromhead));
|
||||
#ifdef AS_STATS
|
||||
fprintf(stderr, "as_cache %u searches, %u hits, %u entries,"
|
||||
" index %zu bytes %u blocks, paths %zu bytes %u blocks\n",
|
||||
as_cache_tries, as_cache_hits,
|
||||
paths,
|
||||
index_sz, index_nb,
|
||||
paths_nb * sizeof(struct as_path), paths_nb);
|
||||
as_cache_hits = as_cache_tries = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct as_path *
|
||||
as_find_cachepath(coord fx, coord fy, coord tx, coord ty)
|
||||
{
|
||||
struct as_frompath *from;
|
||||
struct as_topath *to;
|
||||
|
||||
as_cache_try();
|
||||
/* Is the cache on? if not, return NULL */
|
||||
if (as_cachepath_on == 0)
|
||||
return NULL;
|
||||
|
||||
/* Do we have any cached? */
|
||||
if (fromhead == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Yes! */
|
||||
for (from = fromhead[fy]; from; from = from->next) {
|
||||
if (from->x == fx) {
|
||||
for (to = from->tolist[ty]; to; to = to->next) {
|
||||
if (to->x == tx) {
|
||||
as_cache_hit();
|
||||
return to->path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
50
src/lib/as/as_costcomp.c
Normal file
50
src/lib/as/as_costcomp.c
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include "as.h"
|
||||
|
||||
/*
|
||||
* Compare the lower bound costs of two nodes. If the two nodes have
|
||||
* equal lower bound costs, sort on the secondary field.
|
||||
* Used as comparision function for qsort.
|
||||
*/
|
||||
int
|
||||
as_costcomp(const void *p1, const void *p2)
|
||||
{
|
||||
struct as_node *const *n1 = p1;
|
||||
struct as_node *const *n2 = p2;
|
||||
double diff;
|
||||
|
||||
diff = (*n1)->lbcost - (*n2)->lbcost;
|
||||
if (diff < -0.0001)
|
||||
return -1;
|
||||
if (diff > 0.0001)
|
||||
return 1;
|
||||
|
||||
/* equal, check secondary cost */
|
||||
diff = (*n1)->seccost - (*n2)->seccost;
|
||||
if (diff < -0.0001)
|
||||
return -1;
|
||||
if (diff > 0.0001)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
85
src/lib/as/as_delete.c
Normal file
85
src/lib/as/as_delete.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "as.h"
|
||||
|
||||
static void as_free_queue(struct as_queue *queue);
|
||||
|
||||
/*
|
||||
* Free any dynamically allocated data stored in the as_data structure.
|
||||
*/
|
||||
void
|
||||
as_reset(struct as_data *adp)
|
||||
{
|
||||
as_free_queue(adp->head);
|
||||
adp->head = NULL;
|
||||
as_free_queue(adp->tried);
|
||||
adp->tried = NULL;
|
||||
as_free_queue(adp->subsumed);
|
||||
adp->subsumed = NULL;
|
||||
as_free_hashtab(adp);
|
||||
as_free_path(adp->path);
|
||||
adp->path = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free a queue (either the main, subsumed, or tried).
|
||||
*/
|
||||
static void
|
||||
as_free_queue(struct as_queue *queue)
|
||||
{
|
||||
struct as_queue *qp, *qp2;
|
||||
|
||||
for (qp = queue; qp; qp = qp2) {
|
||||
free(qp->np);
|
||||
qp2 = qp->next;
|
||||
free(qp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Free a path.
|
||||
*/
|
||||
void
|
||||
as_free_path(struct as_path *pp)
|
||||
{
|
||||
struct as_path *pp2;
|
||||
|
||||
for (; pp; pp = pp2) {
|
||||
pp2 = pp->next;
|
||||
free(pp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete the as_data structure (which includes freeing its data).
|
||||
*/
|
||||
void
|
||||
as_delete(struct as_data *adp)
|
||||
{
|
||||
as_reset(adp);
|
||||
free(adp->neighbor_coords);
|
||||
free(adp->neighbor_nodes);
|
||||
free(adp->hashtab);
|
||||
free(adp);
|
||||
}
|
71
src/lib/as/as_extend.c
Normal file
71
src/lib/as/as_extend.c
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "as.h"
|
||||
|
||||
/*
|
||||
* Extend the queue by neighbors. This entails getting the
|
||||
* coordinates of all the neighbors, figuring out their lower bound
|
||||
* costs, throwing away ones that are more expensive than ones we
|
||||
* already have, sorting, tand then merging into the queue.
|
||||
*/
|
||||
struct as_queue *
|
||||
as_extend(struct as_data *adp)
|
||||
{
|
||||
struct as_queue *qp;
|
||||
int i;
|
||||
struct as_queue *head;
|
||||
|
||||
head = adp->head;
|
||||
|
||||
/* Find the neighboring coordinates. */
|
||||
i = adp->neighbor(head->np->c, adp->neighbor_coords, adp->userdata);
|
||||
if (i == 0)
|
||||
return NULL;
|
||||
/*
|
||||
* Get rid of neighbors that are more costly than ones we already have,
|
||||
* and sort the rest into an array of as_nodes.
|
||||
*/
|
||||
i = as_winnow(adp, adp->neighbor_coords, i);
|
||||
if (i < 0)
|
||||
return NULL;
|
||||
if (i > 1)
|
||||
qsort(adp->neighbor_nodes, i,
|
||||
sizeof(*adp->neighbor_nodes), as_costcomp);
|
||||
|
||||
/* remove old coord from head of queue and add to list of tried */
|
||||
qp = head;
|
||||
head = head->next;
|
||||
if (head)
|
||||
head->prev = NULL;
|
||||
if (adp->tried) {
|
||||
adp->tried->prev = qp;
|
||||
qp->next = adp->tried;
|
||||
adp->tried = qp;
|
||||
} else
|
||||
adp->tried = qp;
|
||||
adp->tried->np->flags |= AS_TRIED;
|
||||
|
||||
head = as_merge(adp, head, adp->neighbor_nodes);
|
||||
return head;
|
||||
}
|
83
src/lib/as/as_hash.c
Normal file
83
src/lib/as/as_hash.c
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "as.h"
|
||||
|
||||
/*
|
||||
* Return a pointer to the as_queue structure associated with
|
||||
* this coordinate if the coordinate is in the queue.
|
||||
*/
|
||||
struct as_queue *
|
||||
as_iscinq(struct as_data *adp, struct as_coord c)
|
||||
{
|
||||
int hashval;
|
||||
struct as_hash *hp;
|
||||
|
||||
hashval = adp->hash(c) % adp->hashsize;
|
||||
|
||||
for (hp = adp->hashtab[hashval]; hp; hp = hp->next)
|
||||
if (hp->c.x == c.x && hp->c.y == c.y)
|
||||
return hp->qp;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the queue structure associated with this coordinate.
|
||||
*/
|
||||
void
|
||||
as_setcinq(struct as_data *adp, struct as_coord c, struct as_queue *qp)
|
||||
{
|
||||
int hashval;
|
||||
struct as_hash *hp;
|
||||
struct as_hash *new;
|
||||
|
||||
new = (struct as_hash *)malloc(sizeof(struct as_hash));
|
||||
new->c = c;
|
||||
new->qp = qp;
|
||||
|
||||
hashval = adp->hash(c) % adp->hashsize;
|
||||
hp = adp->hashtab[hashval];
|
||||
|
||||
new->next = (hp) ? hp : NULL;
|
||||
adp->hashtab[hashval] = new;
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk down the hash table array, freeing the chains and zeroing
|
||||
* the chain pointers.
|
||||
*/
|
||||
void
|
||||
as_free_hashtab(struct as_data *adp)
|
||||
{
|
||||
int i;
|
||||
struct as_hash *hp, *hp2;
|
||||
|
||||
for (i = 0; i < adp->hashsize; i++) {
|
||||
for (hp = adp->hashtab[i]; hp; hp = hp2) {
|
||||
hp2 = hp->next;
|
||||
free(hp);
|
||||
}
|
||||
adp->hashtab[i] = NULL;
|
||||
}
|
||||
}
|
59
src/lib/as/as_init.c
Normal file
59
src/lib/as/as_init.c
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "as.h"
|
||||
|
||||
/*
|
||||
* Return an as_data structure with the necessary fields filled in
|
||||
* and space malloced. Return NULL if malloc fails.
|
||||
*/
|
||||
struct as_data *
|
||||
as_init(int maxneighbors,
|
||||
int hashsize,
|
||||
int (*hashfunc)(struct as_coord),
|
||||
int (*neighborfunc)(struct as_coord, struct as_coord *, void *),
|
||||
double (*lbcostfunc)(struct as_coord, struct as_coord, void *),
|
||||
double (*realcostfunc)(struct as_coord, struct as_coord, void *),
|
||||
double (*seccostfunc)(struct as_coord, struct as_coord, void *),
|
||||
void *userdata)
|
||||
{
|
||||
struct as_data *adp;
|
||||
|
||||
AS_NEW(adp, struct as_data, NULL);
|
||||
AS_NEW_ARRAY(adp->neighbor_coords, struct as_coord,
|
||||
maxneighbors, NULL);
|
||||
AS_NEW_ARRAY(adp->neighbor_nodes, struct as_node *,
|
||||
maxneighbors + 1, NULL);
|
||||
AS_NEW_ARRAY(adp->hashtab, struct as_hash *, hashsize, NULL);
|
||||
|
||||
adp->maxneighbors = maxneighbors;
|
||||
adp->hashsize = hashsize;
|
||||
adp->hash = hashfunc;
|
||||
adp->neighbor = neighborfunc;
|
||||
adp->lbcost = lbcostfunc;
|
||||
adp->realcost = realcostfunc;
|
||||
adp->seccost = seccostfunc;
|
||||
adp->userdata = userdata;
|
||||
|
||||
return adp;
|
||||
}
|
83
src/lib/as/as_merge.c
Normal file
83
src/lib/as/as_merge.c
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "as.h"
|
||||
|
||||
/*
|
||||
* Merge neighbors into queue, keeping it sorted. "neighbors" is sorted,
|
||||
* both by lower bound cost and then by secondary cost.
|
||||
*/
|
||||
struct as_queue *
|
||||
as_merge(struct as_data *adp, struct as_queue *head,
|
||||
struct as_node **neighbors)
|
||||
{
|
||||
struct as_queue *qp;
|
||||
struct as_queue *pp; /* previous pointer */
|
||||
struct as_queue *ip; /* insert pointer */
|
||||
struct as_node *np;
|
||||
int i;
|
||||
|
||||
qp = head;
|
||||
pp = NULL;
|
||||
for (i = 0; neighbors[i]; i++) {
|
||||
np = neighbors[i];
|
||||
/* scan until qp points to a node we should go in front of */
|
||||
while (qp && (qp->np->lbcost < np->lbcost)) {
|
||||
pp = qp;
|
||||
qp = qp->next;
|
||||
}
|
||||
/* check for equal lower bounds, and use secondary cost if = */
|
||||
if (qp && qp->np->lbcost == np->lbcost) {
|
||||
while (qp && (qp->np->lbcost == np->lbcost) &&
|
||||
(qp->np->seccost < np->seccost)) {
|
||||
pp = qp;
|
||||
qp = qp->next;
|
||||
}
|
||||
}
|
||||
AS_NEW_MALLOC(ip, struct as_queue, NULL);
|
||||
/* if there was such a node, insert us in front of it */
|
||||
if (qp) {
|
||||
ip->prev = qp->prev;
|
||||
if (ip->prev)
|
||||
ip->prev->next = ip;
|
||||
ip->next = qp;
|
||||
qp->prev = ip;
|
||||
if (qp == head)
|
||||
head = ip;
|
||||
} else { /* otherwise add us to end of queue */
|
||||
ip->next = NULL;
|
||||
ip->prev = pp;
|
||||
if (ip->prev)
|
||||
ip->prev->next = ip;
|
||||
else {
|
||||
head = ip;
|
||||
}
|
||||
pp = ip;
|
||||
}
|
||||
ip->np = np;
|
||||
as_setcinq(adp, np->c, ip);
|
||||
np->step++;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
146
src/lib/as/as_search.c
Normal file
146
src/lib/as/as_search.c
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
/*
|
||||
* 11/09/98 - Steve McClure
|
||||
* Added path list caching structures
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "as.h"
|
||||
|
||||
/*
|
||||
* Basic A* search function. "adp" should have been initialized by
|
||||
* as_init (any previously allocated data will be freed by as_reset here),
|
||||
* and adp->from and adp->to should be set accordingly. On success,
|
||||
* returns 0, with adp->path set to a linked list of coordinates to target.
|
||||
* If we can't find target, return -1; if malloc fails, return -2.
|
||||
*/
|
||||
int
|
||||
as_search(struct as_data *adp)
|
||||
{
|
||||
int iter = 0;
|
||||
struct as_queue *head;
|
||||
struct as_node *np;
|
||||
#ifdef DEBUG
|
||||
int i;
|
||||
struct as_queue *qp;
|
||||
struct as_path *pp;
|
||||
#endif /* DEBUG */
|
||||
|
||||
as_reset(adp);
|
||||
|
||||
/*
|
||||
* Jump start the queue by making first element the zero-cost
|
||||
* node where we start.
|
||||
*/
|
||||
AS_NEW_MALLOC(head, struct as_queue, -2);
|
||||
adp->head = head;
|
||||
head->next = head->prev = NULL;
|
||||
AS_NEW(np, struct as_node, -2);
|
||||
np->c = adp->from;
|
||||
head->np = np;
|
||||
as_setcinq(adp, head->np->c, adp->head);
|
||||
|
||||
for (;;) {
|
||||
iter++;
|
||||
/* see if we're done, one way or another */
|
||||
if (head == NULL)
|
||||
break;
|
||||
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "Iteration %d, head at %d, %d\n", iter,
|
||||
head->np->c.x, head->np->c.y);
|
||||
#endif /* DEBUG */
|
||||
|
||||
/* Add it to the cache */
|
||||
as_add_cachepath(adp);
|
||||
|
||||
if (head->np->c.x == adp->to.x && head->np->c.y == adp->to.y)
|
||||
break;
|
||||
|
||||
/* extend queue by neighbors */
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "\tExtending queue\n");
|
||||
#endif /* DEBUG */
|
||||
adp->head = head = as_extend(adp);
|
||||
/* FIXME leaks when as_extend() returns NULL without adding to tried */
|
||||
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "queue:\n");
|
||||
i = 0;
|
||||
for (qp = head; qp; qp = qp->next) {
|
||||
fprintf(stderr, "\t%d, %d so far %f lb %f sec %f\n",
|
||||
qp->np->c.x, qp->np->c.y,
|
||||
qp->np->knowncost, qp->np->lbcost, qp->np->seccost);
|
||||
i++;
|
||||
}
|
||||
fprintf(stderr, "\tqueue len %d\n", i);
|
||||
#endif /* DEBUG */
|
||||
|
||||
}
|
||||
|
||||
if (head == NULL) {
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "Failed\n");
|
||||
#endif /* DEBUG */
|
||||
return -1;
|
||||
}
|
||||
|
||||
as_makepath(adp);
|
||||
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "Succeeded, iter %d, cost %f!\n", iter,
|
||||
head->np->knowncost);
|
||||
fprintf(stderr, "Path:\n");
|
||||
for (pp = adp->path; pp; pp = pp->next) {
|
||||
fprintf(stderr, "\t%d, %d\n", pp->c.x, pp->c.y);
|
||||
}
|
||||
fprintf(stderr, "Tried queue:\n");
|
||||
for (qp = adp->tried; qp; qp = qp->next) {
|
||||
fprintf(stderr, "\t%d, %d\n", qp->np->c.x, qp->np->c.y);
|
||||
}
|
||||
fprintf(stderr, "Subsumed queue:\n");
|
||||
for (qp = adp->subsumed; qp; qp = qp->next) {
|
||||
fprintf(stderr, "\t%d, %d\n", qp->np->c.x, qp->np->c.y);
|
||||
}
|
||||
#endif /* DEBUG */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Work backwards through the list of nodes (starting at head)
|
||||
* to produce a path.
|
||||
*/
|
||||
void
|
||||
as_makepath(struct as_data *adp)
|
||||
{
|
||||
struct as_path *pp;
|
||||
struct as_node *np;
|
||||
|
||||
for (np = adp->head->np; np; np = np->back) {
|
||||
pp = malloc(sizeof(struct as_path));
|
||||
pp->c = np->c;
|
||||
pp->next = adp->path;
|
||||
adp->path = pp;
|
||||
}
|
||||
}
|
88
src/lib/as/as_stats.c
Normal file
88
src/lib/as/as_stats.c
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include "as.h"
|
||||
|
||||
/*
|
||||
* Print statistics on algorithm performance to the file pointer "fp".
|
||||
*/
|
||||
void
|
||||
as_stats(struct as_data *adp, FILE * fp)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int total_q;
|
||||
int total_p;
|
||||
int total_h;
|
||||
size_t other;
|
||||
struct as_queue *qp;
|
||||
struct as_path *pp;
|
||||
struct as_hash *hp;
|
||||
|
||||
fprintf(fp, "Statistics:\n");
|
||||
|
||||
fprintf(fp, "queue lengths:\n");
|
||||
total_q = 0;
|
||||
total_h = 0;
|
||||
for (i = 0, qp = adp->head; qp; qp = qp->next)
|
||||
i++;
|
||||
fprintf(fp, "\tmain:\t%d\n", i);
|
||||
total_q += i;
|
||||
for (i = 0, qp = adp->tried; qp; qp = qp->next)
|
||||
i++;
|
||||
fprintf(fp, "\ttried:\t%d\n", i);
|
||||
total_q += i;
|
||||
for (i = 0, qp = adp->subsumed; qp; qp = qp->next)
|
||||
i++;
|
||||
fprintf(fp, "\tsubsumed:\t%d\n", i);
|
||||
total_q += i;
|
||||
for (i = 0, pp = adp->path; pp; pp = pp->next)
|
||||
i++;
|
||||
total_p = i;
|
||||
fprintf(fp, "path length: %d\n", total_p);
|
||||
fprintf(fp, "hash table statistics (size %d):\n", adp->hashsize);
|
||||
for (i = 0; i < adp->hashsize; i++) {
|
||||
for (j = 0, hp = adp->hashtab[i]; hp; hp = hp->next)
|
||||
j++;
|
||||
fprintf(fp, "\t%d\t%d\n", i, j);
|
||||
total_h += j;
|
||||
}
|
||||
fprintf(fp, "\ttotal\t%d\n", total_h);
|
||||
fprintf(fp, "approximate memory usage (bytes):\n");
|
||||
fprintf(fp, "\tqueues\t%d\n",
|
||||
(int)(total_q * sizeof(struct as_queue)));
|
||||
fprintf(fp, "\tnodes\t%d\n", (int)(total_q * sizeof(struct as_node)));
|
||||
fprintf(fp, "\tpath\t%d\n", (int)(total_p * sizeof(struct as_path)));
|
||||
fprintf(fp, "\thash ents\t%d\n",
|
||||
(int)(total_h * sizeof(struct as_hash)));
|
||||
other = sizeof(struct as_data);
|
||||
other += adp->maxneighbors * sizeof(struct as_coord);
|
||||
other += (adp->maxneighbors + 1) * sizeof(struct as_node *);
|
||||
other += adp->hashsize * sizeof(struct as_hash *);
|
||||
fprintf(fp, "\tother\t%d\n", (int)other);
|
||||
fprintf(fp, "\ttotal\t%d\n",
|
||||
(int)(total_q * sizeof(struct as_queue) +
|
||||
total_q * sizeof(struct as_node) +
|
||||
total_p * sizeof(struct as_path) +
|
||||
total_h * sizeof(struct as_hash) +
|
||||
other));
|
||||
}
|
174
src/lib/as/as_winnow.c
Normal file
174
src/lib/as/as_winnow.c
Normal file
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* A* Search - A search library used in Empire to determine paths between
|
||||
* objects.
|
||||
* Copyright (C) 1990-1998 Phil Lapsley
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "as.h"
|
||||
|
||||
static struct as_node *as_newnode(struct as_node *backp, struct as_coord c,
|
||||
double inclbcost, double lbcost,
|
||||
double knowncost, double seccost);
|
||||
|
||||
/*
|
||||
* Take a list of neighbor coordinates and winnow them down into
|
||||
* an interesting list of neighbor nodes. This means:
|
||||
*
|
||||
* For each neighbor,
|
||||
* Compute a lower bound on the total cost to target.
|
||||
* If this neighbor is already in our queue,
|
||||
* See if the new neighbor is cheaper.
|
||||
* If so, add it to queue and move the
|
||||
* old node to the subsumed list.
|
||||
* If not, ignore this neighbor.
|
||||
* If this neighbor isn't in the queue, add it.
|
||||
*/
|
||||
int
|
||||
as_winnow(struct as_data *adp, struct as_coord *coords, int ncoords)
|
||||
{
|
||||
int i = 0;
|
||||
int fix_pointer;
|
||||
double knowncost;
|
||||
double incknowncost;
|
||||
double lbcost;
|
||||
double inclbcost;
|
||||
double seccost;
|
||||
struct as_coord *cp;
|
||||
struct as_coord *end;
|
||||
struct as_queue *qp;
|
||||
struct as_node *np;
|
||||
|
||||
for (cp = coords, end = coords + ncoords; cp < end; cp++) {
|
||||
fix_pointer = 0;
|
||||
incknowncost = adp->realcost(adp->head->np->c, *cp, adp->userdata);
|
||||
knowncost = adp->head->np->knowncost + incknowncost;
|
||||
/*
|
||||
* If this neighbor is already in the queue, we can
|
||||
* save some time.
|
||||
*/
|
||||
qp = as_iscinq(adp, *cp);
|
||||
inclbcost = qp ? qp->np->inclbcost :
|
||||
adp->lbcost(*cp, adp->to, adp->userdata);
|
||||
if (inclbcost < 0.0) /* skip bad cases */
|
||||
continue;
|
||||
lbcost = knowncost + inclbcost;
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "\tneighbor %d, %d, lbcost %f ", cp->x, cp->y,
|
||||
lbcost);
|
||||
#endif /* DEBUG */
|
||||
/*
|
||||
* If this neighbor is already in the queue, check to
|
||||
* see which has the lower cost. If the one already in
|
||||
* the queue is cheaper, skip this neighbor as bad. If
|
||||
* the neighbor does, delete the one in the queue.
|
||||
*/
|
||||
if (qp) {
|
||||
if (qp->np->lbcost <= lbcost) {
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "old, loses to %f\n", qp->np->lbcost);
|
||||
#endif /* DEBUG */
|
||||
continue;
|
||||
} else {
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "old, wins over %f\n", qp->np->lbcost);
|
||||
#endif /* DEBUG */
|
||||
if (qp->np->flags & AS_TRIED) {
|
||||
/* should "never happen" */
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
* The neighbor is better than a previously visited coordinate;
|
||||
* remove the old coordinate from the queue and add it to
|
||||
* the subsumed nodes queue. To get here at
|
||||
* all we can't be the head, thus qp->prev is defined.
|
||||
*/
|
||||
/* Delete from main queue */
|
||||
qp->prev->next = qp->next;
|
||||
if (qp->next)
|
||||
qp->next->prev = qp->prev;
|
||||
|
||||
/* Add to subsumed queue */
|
||||
if (adp->subsumed) {
|
||||
adp->subsumed->prev = qp;
|
||||
qp->next = adp->subsumed;
|
||||
} else {
|
||||
qp->next = NULL;
|
||||
}
|
||||
adp->subsumed = qp;
|
||||
adp->subsumed->prev = NULL;
|
||||
fix_pointer = 1;
|
||||
/*
|
||||
* At this point, the as_iscinq code may contain bogus pointer
|
||||
* refs. They'll be fixed when as_merge merges the new
|
||||
* neighbors into the main queue.
|
||||
*/
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else {
|
||||
fprintf(stderr, "new\n");
|
||||
}
|
||||
#endif /* DEBUG */
|
||||
|
||||
if (qp)
|
||||
seccost = qp->np->seccost;
|
||||
else
|
||||
seccost = adp->seccost ?
|
||||
adp->seccost(*cp, adp->to, adp->userdata) : 0.0;
|
||||
np = as_newnode(adp->head->np, *cp, inclbcost, lbcost,
|
||||
knowncost, seccost);
|
||||
if (np == NULL)
|
||||
return 0;
|
||||
if (fix_pointer) {
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "Fixing pointer for %d, %d\n",
|
||||
adp->subsumed->np->c.x, adp->subsumed->np->c.y);
|
||||
#endif
|
||||
adp->subsumed->np->back = np;
|
||||
}
|
||||
adp->neighbor_nodes[i++] = np;
|
||||
|
||||
}
|
||||
adp->neighbor_nodes[i] = NULL;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
static struct as_node *
|
||||
as_newnode(struct as_node *backp, struct as_coord c,
|
||||
double inclbcost, double lbcost, double knowncost,
|
||||
double seccost)
|
||||
{
|
||||
struct as_node *np;
|
||||
|
||||
/* Got an interesting coordinate; make a node for it. */
|
||||
AS_NEW_MALLOC(np, struct as_node, NULL);
|
||||
np->flags = 0;
|
||||
np->c = c;
|
||||
np->inclbcost = inclbcost;
|
||||
np->lbcost = lbcost;
|
||||
np->knowncost = knowncost;
|
||||
np->seccost = seccost;
|
||||
np->step = backp->step;
|
||||
np->back = backp;
|
||||
|
||||
return np;
|
||||
}
|
223
src/lib/common/bestpath.c
Normal file
223
src/lib/common/bestpath.c
Normal file
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Empire - A multi-player, client/server Internet based war game.
|
||||
* Copyright (C) 1986-2011, Dave Pare, Jeff Bailey, Thomas Ruschak,
|
||||
* Ken Stevens, Steve McClure, Markus Armbruster
|
||||
*
|
||||
* Empire is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* See files README, COPYING and CREDITS in the root of the source
|
||||
* tree for related information and legal notices. It is expected
|
||||
* that future projects/authors will amend these files as needed.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* bestpath.c: Find the best path between sectors
|
||||
*
|
||||
* Known contributors to this file:
|
||||
* Steve McClure, 1998-2000
|
||||
* Markus Armbruster, 2006
|
||||
*/
|
||||
|
||||
/*
|
||||
* IMPORTANT: These routines are very selectively used in the server.
|
||||
*
|
||||
* "bestownedpath" is only used to determine paths for ships and planes.
|
||||
*
|
||||
* Callers should not be calling these directly anymore. They should use
|
||||
* the "BestShipPath", "BestAirPath", "BestLandPath" and "BestDistPath"
|
||||
* functions. Note that those last two use the A* algorithms to find
|
||||
* information.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include "file.h"
|
||||
#include "misc.h"
|
||||
#include "nat.h"
|
||||
#include "optlist.h"
|
||||
#include "path.h"
|
||||
#include "prototypes.h"
|
||||
#include "sect.h"
|
||||
#include "xy.h"
|
||||
|
||||
static int owned_and_navigable(char *, int, int, int);
|
||||
|
||||
#define MAXROUTE 100
|
||||
#define valid(x,y) ((((x) ^ (y)) & 1) == 0)
|
||||
|
||||
/*
|
||||
* Ok, note that here we malloc some buffers. BUT, we never
|
||||
* free them. Why, you may ask? Because we want to allocate
|
||||
* them based on world size which is now (or soon to be) dynamic,
|
||||
* but we don't want to allocate each and every time, since that
|
||||
* would be slow. And, since world size only changes at init
|
||||
* time, we can do this safely.
|
||||
*/
|
||||
static unsigned short *mapbuf;
|
||||
static unsigned short **mapindex;
|
||||
|
||||
/*
|
||||
* Find passable path from X, Y to EX, EY for nation OWN.
|
||||
* BPATH is a buffer capable of holding at least MAXROUTE characters.
|
||||
* If BIGMAP is null, all sectors are passable (useful for flying).
|
||||
* Else it is taken to be a bmap.
|
||||
* Sectors owned by or allied to OWN are then passable according to
|
||||
* the usual rules.
|
||||
* Other sectors are assumed to be passable when BIGMAP shows '.' or
|
||||
* nothing.
|
||||
* Return a path if found, else a null pointer.
|
||||
* Wart: the path isn't terminated with 'h', except when if X,Y equals
|
||||
* EX,EY.
|
||||
*/
|
||||
char *
|
||||
bestownedpath(char *bpath, char *bigmap,
|
||||
int x, int y, int ex, int ey, int own)
|
||||
{
|
||||
int i, j, tx, ty, markedsectors;
|
||||
int minx, maxx, miny, maxy, scanx, scany;
|
||||
unsigned routelen;
|
||||
|
||||
if (!mapbuf)
|
||||
mapbuf = malloc(WORLD_X * WORLD_Y * sizeof(*mapbuf));
|
||||
if (!mapbuf)
|
||||
return NULL;
|
||||
if (!mapindex) {
|
||||
mapindex = malloc(WORLD_X * sizeof(*mapindex));
|
||||
if (mapindex) {
|
||||
/* Setup the map pointers */
|
||||
for (i = 0; i < WORLD_X; i++)
|
||||
mapindex[i] = &mapbuf[WORLD_Y * i];
|
||||
}
|
||||
}
|
||||
if (!mapindex)
|
||||
return NULL;
|
||||
|
||||
x = XNORM(x);
|
||||
y = YNORM(y);
|
||||
ex = XNORM(ex);
|
||||
ey = YNORM(ey);
|
||||
|
||||
if (x == ex && y == ey)
|
||||
return "h";
|
||||
|
||||
if (!valid(x, y) || !valid(ex, ey))
|
||||
return NULL;
|
||||
if (!owned_and_navigable(bigmap, ex, ey, own))
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < WORLD_X; i++)
|
||||
for (j = 0; j < WORLD_Y; j++)
|
||||
mapindex[i][j] = 0xFFFF; /* clear the workspace */
|
||||
|
||||
routelen = 0; /* path length is now 0 */
|
||||
mapindex[x][y] = 0; /* mark starting spot */
|
||||
minx = x - 2; /* set X scan bounds */
|
||||
maxx = x + 2;
|
||||
miny = y - 1; /* set Y scan bounds */
|
||||
maxy = y + 1;
|
||||
|
||||
do {
|
||||
if (++routelen == MAXROUTE)
|
||||
return NULL;
|
||||
markedsectors = 0;
|
||||
for (scanx = minx; scanx <= maxx; scanx++) {
|
||||
x = XNORM(scanx);
|
||||
for (scany = miny; scany <= maxy; scany++) {
|
||||
y = YNORM(scany);
|
||||
if (!valid(x, y))
|
||||
continue;
|
||||
if (((mapindex[x][y] & 0x1FFF) == routelen - 1)) {
|
||||
for (i = DIR_FIRST; i <= DIR_LAST; i++) {
|
||||
tx = x + diroff[i][0];
|
||||
ty = y + diroff[i][1];
|
||||
tx = XNORM(tx);
|
||||
ty = YNORM(ty);
|
||||
if (mapindex[tx][ty] == 0xFFFF) {
|
||||
if (owned_and_navigable(bigmap, tx, ty, own)) {
|
||||
if (CANT_HAPPEN(i < DIR_FIRST || i > DIR_LAST))
|
||||
i = DIR_STOP;
|
||||
mapindex[tx][ty] =
|
||||
((i - DIR_FIRST + 1) << 13) + routelen;
|
||||
markedsectors++;
|
||||
}
|
||||
}
|
||||
if (tx == ex && ty == ey) {
|
||||
bpath[routelen] = 0;
|
||||
while (routelen--) {
|
||||
i = (mapindex[tx][ty] >> 13)
|
||||
- 1 + DIR_FIRST;
|
||||
bpath[routelen] = dirch[i];
|
||||
tx = tx - diroff[i][0];
|
||||
ty = ty - diroff[i][1];
|
||||
tx = XNORM(tx);
|
||||
ty = YNORM(ty);
|
||||
}
|
||||
return bpath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
miny--;
|
||||
maxy++;
|
||||
minx -= 2;
|
||||
maxx += 2;
|
||||
} while (markedsectors);
|
||||
|
||||
return NULL; /* no route possible */
|
||||
}
|
||||
|
||||
/*
|
||||
* Return non-zero if sector X, Y is passable.
|
||||
* If BIGMAP is null, all sectors are passable (useful for flying).
|
||||
* Else it is taken to be a bmap.
|
||||
* Sectors owned by or allied to OWN are checked according to the
|
||||
* usual rules, and the result is correct.
|
||||
* Other sectors are assumed to be passable when BIGMAP shows '.' or
|
||||
* nothing.
|
||||
*/
|
||||
static int
|
||||
owned_and_navigable(char *bigmap, int x, int y, int own)
|
||||
{
|
||||
char mapspot;
|
||||
struct sctstr sect;
|
||||
|
||||
if (!bigmap)
|
||||
return 1;
|
||||
|
||||
/* Owned or allied sector? Check the real sector. */
|
||||
getsect(x, y, §);
|
||||
if (sect.sct_own && relations_with(sect.sct_own, own) == ALLIED) {
|
||||
/* FIXME duplicates shp_check_nav() logic */
|
||||
switch (dchr[sect.sct_type].d_nav) {
|
||||
case NAVOK:
|
||||
return 1;
|
||||
case NAV_CANAL:
|
||||
/* FIXME return 1 when all ships have M_CANAL */
|
||||
return 0;
|
||||
case NAV_02:
|
||||
return sect.sct_effic >= 2;
|
||||
case NAV_60:
|
||||
return sect.sct_effic >= 60;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Can only check bigmap */
|
||||
mapspot = bigmap[sect.sct_uid];
|
||||
return mapspot == '.' || mapspot == ' ' || mapspot == 0;
|
||||
}
|
|
@ -34,15 +34,45 @@
|
|||
* Markus Armbruster, 2011
|
||||
*/
|
||||
|
||||
/*
|
||||
* Define AS_STATS for A* statistics on stderr.
|
||||
*
|
||||
* Define AS_NO_PATH_CACHE to disable the path cache. The path cache
|
||||
* saves a lot of work, but uses lots of memory. It should be a
|
||||
* significant net win, unless you run out of memory.
|
||||
*
|
||||
* Define AS_NO_NEIGHBOR_CACHE to disable the neighbor cache. The
|
||||
* neighbor cache trades a modest amount of memory to save a bit of
|
||||
* work. In its current form, it doesn't really make a difference.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <string.h>
|
||||
#include "../as/as.h"
|
||||
#include "file.h"
|
||||
#include "optlist.h"
|
||||
#include "path.h"
|
||||
#include "prototypes.h"
|
||||
#include "sect.h"
|
||||
#include "xy.h"
|
||||
|
||||
#ifdef USE_PATH_FIND
|
||||
void
|
||||
bp_enable_cachepath(void)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
bp_disable_cachepath(void)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
bp_clear_cachepath(void)
|
||||
{
|
||||
}
|
||||
|
||||
char *
|
||||
BestLandPath(char *path,
|
||||
struct sctstr *from,
|
||||
|
@ -112,3 +142,397 @@ BestAirPath(char *path, int fx, int fy, int tx, int ty)
|
|||
strcpy(path, "h");
|
||||
return path;
|
||||
}
|
||||
#else /* !USE_PATH_FIND */
|
||||
#define BP_ASHASHSIZE 128 /* A* queue hash table size */
|
||||
#define BP_NEIGHBORS 6 /* max number of neighbors */
|
||||
|
||||
struct bestp {
|
||||
int bp_mobtype;
|
||||
struct as_data *adp;
|
||||
};
|
||||
|
||||
static int bp_path(struct as_path *pp, char *buf);
|
||||
static int bp_neighbors(struct as_coord c, struct as_coord *cp, void *);
|
||||
static double bp_lbcost(struct as_coord from, struct as_coord to, void *);
|
||||
static double bp_realcost(struct as_coord from, struct as_coord to, void *);
|
||||
static double bp_seccost(struct as_coord from, struct as_coord to, void *);
|
||||
static int bp_coord_hash(struct as_coord c);
|
||||
|
||||
/* We use this for caching neighbors. It never changes except
|
||||
* at reboot time (maybe) so we never need to free it */
|
||||
static struct sctstr **neighsects;
|
||||
|
||||
static struct bestp *
|
||||
bp_init(void)
|
||||
{
|
||||
struct bestp *bp;
|
||||
|
||||
bp = malloc(sizeof(*bp));
|
||||
memset(bp, 0, sizeof(*bp));
|
||||
bp->adp = as_init(BP_NEIGHBORS, BP_ASHASHSIZE, bp_coord_hash,
|
||||
bp_neighbors, bp_lbcost, bp_realcost,
|
||||
bp_seccost, bp);
|
||||
|
||||
if (bp->adp == NULL)
|
||||
return NULL;
|
||||
|
||||
#ifndef AS_NO_NEIGHBOR_CACHE
|
||||
if (neighsects == NULL)
|
||||
neighsects = calloc(WORLD_SZ() * 6, sizeof(struct sctstr *));
|
||||
#endif
|
||||
|
||||
return bp;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the best path from sector to to sector, and put the Empire movement
|
||||
* string in path. Return 0 on success, -1 on error.
|
||||
* FIXME unsafe by design: assumes path[] has space; buffer overrun
|
||||
* when path gets long!
|
||||
*/
|
||||
static int
|
||||
best_path(struct sctstr *from, struct sctstr *to, char *path, int mob_type)
|
||||
{
|
||||
static struct bestp *mybestpath;
|
||||
struct as_data *adp;
|
||||
struct as_path *ap;
|
||||
int res;
|
||||
|
||||
if (!mybestpath)
|
||||
mybestpath = bp_init();
|
||||
adp = mybestpath->adp;
|
||||
#ifdef AS_NO_PATH_CACHE
|
||||
ap = NULL;
|
||||
#else
|
||||
ap = as_find_cachepath(from->sct_x, from->sct_y, to->sct_x, to->sct_y);
|
||||
#endif
|
||||
if (ap == NULL) {
|
||||
adp->from.x = from->sct_x;
|
||||
adp->from.y = from->sct_y;
|
||||
adp->to.x = to->sct_x;
|
||||
adp->to.y = to->sct_y;
|
||||
mybestpath->bp_mobtype = mob_type;
|
||||
|
||||
res = as_search(adp);
|
||||
#ifdef AS_STATS
|
||||
as_stats(adp, stderr);
|
||||
#ifndef AS_NO_NEIGHBOR_CACHE
|
||||
fprintf(stderr, "neighbor cache %zu bytes\n",
|
||||
WORLD_SZ() * 6 * sizeof(struct sctstr *));
|
||||
#endif
|
||||
#endif
|
||||
if (res < 0)
|
||||
return -1;
|
||||
ap = adp->path;
|
||||
}
|
||||
|
||||
if (bp_path(ap, path) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Translate an A* path into an empire movement string. Return 0 on
|
||||
* success, -1 on failure.
|
||||
*/
|
||||
static int
|
||||
bp_path(struct as_path *pp, char *buf)
|
||||
{
|
||||
struct as_path *np;
|
||||
char *cp = buf;
|
||||
int dx, dy;
|
||||
int n;
|
||||
|
||||
np = pp->next;
|
||||
while (np) {
|
||||
dx = np->c.x - pp->c.x;
|
||||
/* deal with wraparound from non-neg coords */
|
||||
if (dx < -2)
|
||||
dx += WORLD_X;
|
||||
else if (dx > 2)
|
||||
dx -= WORLD_X;
|
||||
dy = np->c.y - pp->c.y;
|
||||
if (dy < -1)
|
||||
dy += WORLD_Y;
|
||||
else if (dy > 1)
|
||||
dy -= WORLD_Y;
|
||||
for (n = 1; n <= 6; n++)
|
||||
if (dx == diroff[n][0] && dy == diroff[n][1])
|
||||
break;
|
||||
if (n > 6)
|
||||
return -1;
|
||||
|
||||
*cp++ = dirch[n];
|
||||
pp = np;
|
||||
np = np->next;
|
||||
}
|
||||
*cp = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find coords neighboring this sector; return number of such
|
||||
* coords, and coordinartes themselves in an array pointed
|
||||
* to by *cpp.
|
||||
* XXX need to check ownership, sector types, etc.
|
||||
*/
|
||||
static int
|
||||
bp_neighbors(struct as_coord c, struct as_coord *cp, void *pp)
|
||||
{
|
||||
struct sctstr *sectp = (void *)empfile[EF_SECTOR].cache;
|
||||
struct bestp *bp = pp;
|
||||
coord x, y;
|
||||
coord nx, ny;
|
||||
int n = 0, i;
|
||||
struct sctstr *sp, *from, **ssp;
|
||||
/* Six pointers, just in case our cache isn't there */
|
||||
struct sctstr *tsp[] = { NULL, NULL, NULL, NULL, NULL, NULL };
|
||||
int sx, sy, offset;
|
||||
|
||||
x = c.x;
|
||||
y = c.y;
|
||||
sx = XNORM(x);
|
||||
sy = YNORM(y);
|
||||
offset = XYOFFSET(sx, sy);
|
||||
from = §p[offset];
|
||||
|
||||
if (neighsects == NULL)
|
||||
ssp = (struct sctstr **)&tsp[0];
|
||||
else
|
||||
ssp = (struct sctstr **)&neighsects[offset * 6];
|
||||
for (i = 1; i <= 6; i++, ssp++) {
|
||||
if (*ssp == NULL) {
|
||||
/* We haven't cached this neighbor yet */
|
||||
nx = x + diroff[i][0];
|
||||
ny = y + diroff[i][1];
|
||||
sx = XNORM(nx);
|
||||
sy = YNORM(ny);
|
||||
offset = XYOFFSET(sx, sy);
|
||||
sp = §p[offset];
|
||||
*ssp = sp;
|
||||
} else {
|
||||
sp = *ssp;
|
||||
sx = sp->sct_x;
|
||||
sy = sp->sct_y;
|
||||
}
|
||||
/* No need to calculate cost each time, just make sure we can
|
||||
move through it. We calculate it later. */
|
||||
if (dchr[sp->sct_type].d_mob0 < 0)
|
||||
continue;
|
||||
if (bp->bp_mobtype == MOB_RAIL && !SCT_HAS_RAIL(sp))
|
||||
continue;
|
||||
if (sp->sct_own != from->sct_own)
|
||||
continue;
|
||||
cp[n].x = sx;
|
||||
cp[n].y = sy;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute a lower-bound on the cost from "from" to "to".
|
||||
*/
|
||||
/*ARGSUSED*/
|
||||
static double
|
||||
bp_lbcost(struct as_coord from, struct as_coord to, void *pp)
|
||||
{
|
||||
struct sctstr *sectp = (void *)empfile[EF_SECTOR].cache;
|
||||
struct bestp *bp = pp;
|
||||
int x, y, sx, sy, offset;
|
||||
|
||||
x = to.x;
|
||||
y = to.y;
|
||||
sx = XNORM(x);
|
||||
sy = YNORM(y);
|
||||
offset = XYOFFSET(sx, sy);
|
||||
return sector_mcost(§p[offset], bp->bp_mobtype);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the real cost to move from "from" to "to".
|
||||
*/
|
||||
static double
|
||||
bp_realcost(struct as_coord from, struct as_coord to, void *pp)
|
||||
{
|
||||
return bp_lbcost(from, to, pp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tie breaker secondary metric (only used when lower bound costs
|
||||
* are equal).
|
||||
*/
|
||||
/*ARGSUSED*/
|
||||
static double
|
||||
bp_seccost(struct as_coord from, struct as_coord to, void *pp)
|
||||
{
|
||||
return mapdist((coord)from.x, (coord)from.y,
|
||||
(coord)to.x, (coord)to.y);
|
||||
}
|
||||
|
||||
/*
|
||||
* Hash a coordinate into an integer.
|
||||
*/
|
||||
static int
|
||||
bp_coord_hash(struct as_coord c)
|
||||
{
|
||||
return ((abs(c.x) + 1) << 3) ^ abs(c.y);
|
||||
}
|
||||
|
||||
void
|
||||
bp_enable_cachepath(void)
|
||||
{
|
||||
#ifndef AS_NO_PATH_CACHE
|
||||
as_enable_cachepath();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
bp_disable_cachepath(void)
|
||||
{
|
||||
#ifndef AS_NO_PATH_CACHE
|
||||
as_disable_cachepath();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
bp_clear_cachepath(void)
|
||||
{
|
||||
#ifndef AS_NO_PATH_CACHE
|
||||
as_clear_cachepath();
|
||||
#endif
|
||||
}
|
||||
|
||||
double
|
||||
pathcost(struct sctstr *start, char *path, int mob_type)
|
||||
{
|
||||
struct sctstr *sectp = (void *)empfile[EF_SECTOR].cache;
|
||||
unsigned i;
|
||||
int o;
|
||||
int cx, cy;
|
||||
double cost = 0.0;
|
||||
struct sctstr *sp;
|
||||
int sx, sy, offset;
|
||||
|
||||
cx = start->sct_x;
|
||||
cy = start->sct_y;
|
||||
|
||||
while (*path) {
|
||||
if (*path == 'h') {
|
||||
path++;
|
||||
continue;
|
||||
}
|
||||
i = *path - 'a';
|
||||
if (CANT_HAPPEN(i >= sizeof(dirindex) / sizeof(*dirindex)))
|
||||
break;
|
||||
o = dirindex[i];
|
||||
if (CANT_HAPPEN(o < 0))
|
||||
break;
|
||||
cx += diroff[o][0];
|
||||
cy += diroff[o][1];
|
||||
sx = XNORM(cx);
|
||||
sy = YNORM(cy);
|
||||
offset = XYOFFSET(sx, sy);
|
||||
sp = §p[offset];
|
||||
cost += sector_mcost(sp, mob_type);
|
||||
path++;
|
||||
}
|
||||
|
||||
return cost;
|
||||
}
|
||||
|
||||
char *
|
||||
BestDistPath(char *path,
|
||||
struct sctstr *from,
|
||||
struct sctstr *to, double *cost)
|
||||
{
|
||||
return BestLandPath(path, from, to, cost, MOB_MOVE);
|
||||
}
|
||||
|
||||
char *
|
||||
BestLandPath(char *path,
|
||||
struct sctstr *from,
|
||||
struct sctstr *to, double *cost, int mob_type)
|
||||
{
|
||||
int length;
|
||||
|
||||
*path = 0;
|
||||
*cost = 0.0;
|
||||
if (best_path(from, to, path, mob_type) < 0)
|
||||
return NULL;
|
||||
*cost = pathcost(from, path, mob_type);
|
||||
length = strlen(path);
|
||||
path[length] = 'h';
|
||||
path[length + 1] = '\0';
|
||||
return path;
|
||||
}
|
||||
|
||||
char *
|
||||
BestShipPath(char *path, int fx, int fy, int tx, int ty, int owner)
|
||||
{
|
||||
char *map;
|
||||
|
||||
map = ef_ptr(EF_BMAP, owner);
|
||||
if (!map)
|
||||
return NULL;
|
||||
#ifdef TEST_PATH_FIND
|
||||
double newc = path_find(fx, fy, tx, ty, owner, MOB_SAIL);
|
||||
char *p = bestownedpath(path, map, fx, fy, tx, ty, owner);
|
||||
double c;
|
||||
size_t l;
|
||||
char buf[MAX_PATH_LEN];
|
||||
|
||||
if (!p)
|
||||
c = -1.0;
|
||||
else {
|
||||
l = strlen(p);
|
||||
if (p[l-1] == 'h')
|
||||
l--;
|
||||
c = l;
|
||||
}
|
||||
|
||||
if (c != newc) {
|
||||
path_find_route(buf, sizeof(buf), fx, fy, tx, ty);
|
||||
printf("%d,%d -> %d,%d %d: old %g, new %g, %g off\n",
|
||||
fx, fy, tx, ty, MOB_FLY, c, newc, c - newc);
|
||||
printf("\told: %s\n", p);
|
||||
printf("\tnew: %s\n", buf);
|
||||
}
|
||||
return p;
|
||||
#else
|
||||
return bestownedpath(path, map, fx, fy, tx, ty, owner);
|
||||
#endif
|
||||
}
|
||||
|
||||
char *
|
||||
BestAirPath(char *path, int fx, int fy, int tx, int ty)
|
||||
{
|
||||
#ifdef TEST_PATH_FIND
|
||||
double newc = path_find(fx, fy, tx, ty, 0, MOB_FLY);
|
||||
char *p = bestownedpath(path, NULL, fx, fy, tx, ty, -1);
|
||||
double c;
|
||||
size_t l;
|
||||
char buf[MAX_PATH_LEN];
|
||||
|
||||
if (!p)
|
||||
c = -1.0;
|
||||
else {
|
||||
l = strlen(p);
|
||||
if (p[l-1] == 'h')
|
||||
l--;
|
||||
c = l;
|
||||
}
|
||||
|
||||
if (c != newc) {
|
||||
path_find_route(buf, sizeof(buf), fx, fy, tx, ty);
|
||||
printf("%d,%d -> %d,%d %d: old %g, new %g, %g off\n",
|
||||
fx, fy, tx, ty, MOB_FLY, c, newc, c - newc);
|
||||
printf("\told: %s\n", p);
|
||||
printf("\tnew: %s\n", buf);
|
||||
}
|
||||
return p;
|
||||
#else
|
||||
return bestownedpath(path, NULL, fx, fy, tx, ty, -1);
|
||||
#endif
|
||||
}
|
||||
#endif /* !USE_PATH_FIND */
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "file.h"
|
||||
#include "nat.h"
|
||||
#include "optlist.h"
|
||||
#include "prototypes.h"
|
||||
#include "path.h"
|
||||
#include "sect.h"
|
||||
|
||||
|
@ -51,8 +52,8 @@
|
|||
static char *bufrotate(char *buf, size_t bufsz, size_t i);
|
||||
|
||||
/*
|
||||
* Dijkstra's algorithm. Refer to your graph algorithm textbook for
|
||||
* how it works. Implementation is specialized to hex maps.
|
||||
* A* algorithm. Refer to your graph algorithm textbook for how it
|
||||
* works. Implementation is specialized to hex maps.
|
||||
*
|
||||
* Define PATH_FIND_STATS for performance statistics on stdout.
|
||||
*/
|
||||
|
@ -101,7 +102,7 @@ static struct pf_map *pf_map;
|
|||
struct pf_heap {
|
||||
int uid; /* sector uid and */
|
||||
coord x, y; /* coordinates, uid == XYOFFSET(x, y) */
|
||||
double cost; /* cost from source */
|
||||
double f; /* estimated cost from source to dest */
|
||||
};
|
||||
|
||||
static int pf_nheap; /* #entries in pf_nheap[] */
|
||||
|
@ -114,12 +115,13 @@ static coord pf_sx, pf_sy;
|
|||
static int pf_suid;
|
||||
static natid pf_actor;
|
||||
static double (*pf_sct_cost)(natid, int);
|
||||
static double (*pf_h)(coord, coord, coord, coord, double);
|
||||
|
||||
/*
|
||||
* Performance statistics
|
||||
*/
|
||||
#ifdef PATH_FIND_STATS
|
||||
static unsigned pf_nsearch, pf_nsource, pf_nopen, pf_nclose;
|
||||
static unsigned pf_nsearch, pf_nsource, pf_nopen, pf_nreopen, pf_nclose;
|
||||
static unsigned pf_nheap_max, pf_noway;
|
||||
static double pf_sumcost;
|
||||
#define STAT_INC(v) ((void)((v)++))
|
||||
|
@ -131,14 +133,12 @@ static double pf_sumcost;
|
|||
#define STAT_HIMARK(v, h) ((void)0)
|
||||
#endif /* !PATH_FIND_STATS */
|
||||
|
||||
#ifndef NDEBUG /* silence "not used" warning */
|
||||
/* Is sector with uid UID open? */
|
||||
static int
|
||||
pf_is_open(int uid)
|
||||
{
|
||||
return pf_map[uid].visit == pf_visit;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Is sector with uid UID closed? */
|
||||
static int
|
||||
|
@ -169,11 +169,11 @@ pf_check(void)
|
|||
assert(0 <= uid && uid < WORLD_SZ());
|
||||
assert(pf_map[uid].heapi == i);
|
||||
assert(pf_map[uid].visit == pf_visit);
|
||||
assert(pf_map[uid].cost <= pf_heap[i].cost);
|
||||
assert(pf_map[uid].cost <= pf_heap[i].f);
|
||||
c = 2 * i + 1;
|
||||
assert(c >= pf_nheap || pf_heap[i].cost <= pf_heap[c].cost);
|
||||
assert(c >= pf_nheap || pf_heap[i].f <= pf_heap[c].f);
|
||||
c++;
|
||||
assert(c >= pf_nheap || pf_heap[i].cost <= pf_heap[c].cost);
|
||||
assert(c >= pf_nheap || pf_heap[i].f <= pf_heap[c].f);
|
||||
}
|
||||
|
||||
for (uid = 0; uid < WORLD_SZ(); uid++) {
|
||||
|
@ -211,9 +211,9 @@ pf_sift_down(int n)
|
|||
|
||||
assert(0 <= n && n < pf_nheap);
|
||||
for (r = n; (c = 2 * r + 1) < pf_nheap; r = c) {
|
||||
if (c + 1 < pf_nheap && pf_heap[c].cost > pf_heap[c + 1].cost)
|
||||
if (c + 1 < pf_nheap && pf_heap[c].f > pf_heap[c + 1].f)
|
||||
c++;
|
||||
if (pf_heap[r].cost < pf_heap[c].cost)
|
||||
if (pf_heap[r].f < pf_heap[c].f)
|
||||
break;
|
||||
pf_heap_swap(r, c);
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ pf_sift_up(int n)
|
|||
|
||||
assert(0 <= n && n < pf_nheap);
|
||||
for (c = n; (p = (c - 1) / 2), c > 0; c = p) {
|
||||
if (pf_heap[p].cost < pf_heap[c].cost)
|
||||
if (pf_heap[p].f < pf_heap[c].f)
|
||||
break;
|
||||
pf_heap_swap(p, c);
|
||||
}
|
||||
|
@ -237,25 +237,32 @@ pf_sift_up(int n)
|
|||
* Open the unvisited sector X,Y.
|
||||
* UID is sector uid, it equals XYOFFSET(X,Y).
|
||||
* Cheapest path from source comes from direction DIR and has cost COST.
|
||||
* H is the estimated cost from X,Y to the destination.
|
||||
*/
|
||||
static void
|
||||
pf_open(int uid, coord x, coord y, int dir, double cost)
|
||||
pf_open(int uid, coord x, coord y, int dir, double cost, double h)
|
||||
{
|
||||
int i;
|
||||
|
||||
STAT_INC(pf_nopen);
|
||||
i = pf_nheap++;
|
||||
STAT_HIMARK(pf_nheap_max, (unsigned)pf_nheap);
|
||||
DPRINTF("pf: open %d,%d %g %c %d\n", x, y, cost, dirch[dir], i);
|
||||
assert(pf_is_unvisited(uid));
|
||||
pf_map[uid].visit = pf_visit;
|
||||
if (pf_is_open(uid)) {
|
||||
STAT_INC(pf_nreopen);
|
||||
i = pf_map[uid].heapi;
|
||||
DPRINTF("pf: reopen %d,%d %g %c %d\n", x, y, cost, dirch[dir], i);
|
||||
} else {
|
||||
assert(pf_is_unvisited(uid));
|
||||
STAT_INC(pf_nopen);
|
||||
i = pf_nheap++;
|
||||
STAT_HIMARK(pf_nheap_max, (unsigned)pf_nheap);
|
||||
DPRINTF("pf: open %d,%d %g %c %d\n", x, y, cost, dirch[dir], i);
|
||||
pf_map[uid].heapi = i;
|
||||
pf_map[uid].visit = pf_visit;
|
||||
pf_heap[i].uid = uid;
|
||||
pf_heap[i].x = x;
|
||||
pf_heap[i].y = y;
|
||||
}
|
||||
pf_map[uid].dir = dir;
|
||||
pf_map[uid].heapi = i;
|
||||
pf_map[uid].cost = cost;
|
||||
pf_heap[i].uid = uid;
|
||||
pf_heap[i].x = x;
|
||||
pf_heap[i].y = y;
|
||||
pf_heap[i].cost = cost;
|
||||
pf_heap[i].f = cost + h;
|
||||
|
||||
pf_sift_up(i);
|
||||
pf_check();
|
||||
|
@ -285,7 +292,7 @@ pf_close(void)
|
|||
#ifdef PATH_FIND_DEBUG
|
||||
/*
|
||||
* Return cost from source to sector with uid UID.
|
||||
* It must be visited, i.e. open or closed.
|
||||
* It must be open (cost is an estimate) or closed (cost is exact).
|
||||
*/
|
||||
static double
|
||||
pf_cost(int uid)
|
||||
|
@ -337,9 +344,17 @@ rev_dir(int dir)
|
|||
* SX,SY is the source.
|
||||
* The cost to enter the sector with uid u is COST(ACTOR, u).
|
||||
* Negative value means the sector can't be entered.
|
||||
* Optional H() is the heuristic: the cost from x,y to the destination
|
||||
* dx,dy is at least H(x, y, dx, dy, c1d), where c1d is the cost to
|
||||
* enter dx,dy. The closer H() is to the true cost, the more
|
||||
* efficient this function works.
|
||||
* With a null H(), A* degenerates into Dijkstra's algorithm. You can
|
||||
* then call path_find_to() multiple times for the same source. This
|
||||
* is faster when you need many destinations.
|
||||
*/
|
||||
static void
|
||||
pf_set_source(coord sx, coord sy, natid actor, double (*cost)(natid, int))
|
||||
pf_set_source(coord sx, coord sy, natid actor, double (*cost) (natid, int),
|
||||
double (*h) (coord, coord, coord, coord, double))
|
||||
{
|
||||
STAT_INC(pf_nsource);
|
||||
DPRINTF("pf: source %d,%d\n", sx, sy);
|
||||
|
@ -348,6 +363,7 @@ pf_set_source(coord sx, coord sy, natid actor, double (*cost)(natid, int))
|
|||
pf_suid = XYOFFSET(sx, sy);
|
||||
pf_actor = actor;
|
||||
pf_sct_cost = cost;
|
||||
pf_h = h;
|
||||
|
||||
if (!pf_map) {
|
||||
pf_map = calloc(WORLD_SZ(), sizeof(*pf_map));
|
||||
|
@ -361,8 +377,6 @@ pf_set_source(coord sx, coord sy, natid actor, double (*cost)(natid, int))
|
|||
pf_visit += 2;
|
||||
|
||||
pf_nheap = 0;
|
||||
|
||||
pf_open(pf_suid, pf_sx, pf_sy, DIR_STOP, 0.0);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -373,7 +387,7 @@ path_find_to(coord dx, coord dy)
|
|||
{
|
||||
int duid;
|
||||
int uid, nuid, i;
|
||||
double cost, c1;
|
||||
double c1d, cost, c1;
|
||||
coord x, y, nx, ny;
|
||||
|
||||
STAT_INC(pf_nsearch);
|
||||
|
@ -385,29 +399,38 @@ path_find_to(coord dx, coord dy)
|
|||
return pf_map[duid].cost;
|
||||
}
|
||||
|
||||
c1d = pf_sct_cost(pf_actor, duid);
|
||||
if (c1d < 0) {
|
||||
DPRINTF("pf: done new %g\n", -1.0);
|
||||
STAT_INC(pf_noway);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pf_is_unvisited(pf_suid))
|
||||
/* first search from this source */
|
||||
pf_open(pf_suid, pf_sx, pf_sy, DIR_STOP, 0.0,
|
||||
pf_h ? pf_h(pf_sx, pf_sy, dx, dy, c1d) : 0.0);
|
||||
else
|
||||
assert(!pf_h); /* multiple searches only w/o heuristic */
|
||||
|
||||
while (pf_nheap > 0 && (uid = pf_heap[0].uid) != duid) {
|
||||
x = pf_heap[0].x;
|
||||
y = pf_heap[0].y;
|
||||
cost = pf_heap[0].cost;
|
||||
pf_close();
|
||||
cost = pf_map[uid].cost;
|
||||
|
||||
for (i = 0; i < 6; i++) { /* for all neighbors */
|
||||
nx = x_in_dir(x, DIR_FIRST + i);
|
||||
ny = y_in_dir(y, DIR_FIRST + i);
|
||||
nuid = XYOFFSET(nx, ny);
|
||||
/*
|
||||
* Cost to enter NX,NY doesn't depend on direction of
|
||||
* entry. This X,Y is at least as expensive as any
|
||||
* previous one. Therefore, cost to go to NX,NY via X,Y
|
||||
* is at least as high as any previously found route.
|
||||
* Skip neighbors that have a route already.
|
||||
*/
|
||||
if (!pf_is_unvisited(nuid))
|
||||
if (pf_h ? pf_is_closed(nuid) : !pf_is_unvisited(nuid))
|
||||
continue;
|
||||
c1 = pf_sct_cost(pf_actor, nuid);
|
||||
if (c1 < 0)
|
||||
continue;
|
||||
pf_open(nuid, nx, ny, DIR_FIRST + i, cost + c1);
|
||||
if (pf_is_unvisited(nuid) || cost + c1 < pf_map[nuid].cost)
|
||||
pf_open(nuid, nx, ny, DIR_FIRST + i, cost + c1,
|
||||
pf_h ? pf_h(nx, ny, dx, dy, c1d) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -555,9 +578,10 @@ path_find_visualize(coord sx, coord sy, coord dx, coord dy)
|
|||
void
|
||||
path_find_print_stats(void)
|
||||
{
|
||||
printf("pathfind %u searches, %u sources, %u opened, %u closed,"
|
||||
printf("pathfind %u searches, %u sources,"
|
||||
" %u opened, %u reopened, %u closed,"
|
||||
" %u heap max, %zu bytes, %u noway, %g avg cost\n",
|
||||
pf_nsearch, pf_nsource, pf_nopen, pf_nclose,
|
||||
pf_nsearch, pf_nsource, pf_nopen, pf_nreopen, pf_nclose,
|
||||
pf_nheap_max,
|
||||
(WORLD_SZ() * (sizeof(*pf_map) + sizeof(*pf_heap))),
|
||||
pf_noway, pf_nsearch ? pf_sumcost / pf_nsearch : 0.0);
|
||||
|
@ -599,6 +623,20 @@ cost_rail(natid actor, int uid)
|
|||
return cost_land(actor, uid, MOB_RAIL);
|
||||
}
|
||||
|
||||
static double
|
||||
h_move(coord x, coord y, coord dx, coord dy, double c1d)
|
||||
{
|
||||
int md = mapdist(x, y, dx, dy);
|
||||
return md ? c1d + (md - 1) * 0.001 : 0.0;
|
||||
}
|
||||
|
||||
static double
|
||||
h_march_rail(coord x, coord y, coord dx, coord dy, double c1d)
|
||||
{
|
||||
int md = mapdist(x, y, dx, dy);
|
||||
return md ? c1d + (md - 1) * 0.02 : 0.0;
|
||||
}
|
||||
|
||||
static double
|
||||
cost_sail(natid actor, int uid)
|
||||
{
|
||||
|
@ -634,10 +672,20 @@ cost_fly(natid actor, int uid)
|
|||
return 1.0;
|
||||
}
|
||||
|
||||
static double
|
||||
h_sail_fly(coord x, coord y, coord dx, coord dy, double c1d)
|
||||
{
|
||||
return mapdist(x, y, dx, dy);
|
||||
}
|
||||
|
||||
static double (*cost_tab[])(natid, int) = {
|
||||
cost_move, cost_march, cost_rail, cost_sail, cost_fly
|
||||
};
|
||||
|
||||
static double (*h[])(coord, coord, coord, coord, double) = {
|
||||
h_move, h_march_rail, h_march_rail, h_sail_fly, h_sail_fly
|
||||
};
|
||||
|
||||
/*
|
||||
* Start finding paths from SX,SY.
|
||||
* Use mobility costs for ACTOR and MOBTYPE.
|
||||
|
@ -645,7 +693,7 @@ static double (*cost_tab[])(natid, int) = {
|
|||
void
|
||||
path_find_from(coord sx, coord sy, natid actor, int mobtype)
|
||||
{
|
||||
pf_set_source(sx, sy, actor, cost_tab[mobtype]);
|
||||
pf_set_source(sx, sy, actor, cost_tab[mobtype], NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -655,6 +703,6 @@ path_find_from(coord sx, coord sy, natid actor, int mobtype)
|
|||
double
|
||||
path_find(coord sx, coord sy, coord dx, coord dy, natid actor, int mobtype)
|
||||
{
|
||||
pf_set_source(sx, sy, actor, cost_tab[mobtype]);
|
||||
pf_set_source(sx, sy, actor, cost_tab[mobtype], h[mobtype]);
|
||||
return path_find_to(dx, dy);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
|
||||
#include <config.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/resource.h>
|
||||
#include "distribute.h"
|
||||
|
@ -77,7 +79,19 @@ finish_sects(int etu)
|
|||
|
||||
logerror("assembling paths...\n");
|
||||
getrusage(RUSAGE_SELF, &rus1);
|
||||
|
||||
/* First, enable the best_path cacheing */
|
||||
bp_enable_cachepath();
|
||||
|
||||
/* Now assemble the paths */
|
||||
assemble_dist_paths(import_cost);
|
||||
|
||||
/* Now disable the best_path cacheing */
|
||||
bp_disable_cachepath();
|
||||
|
||||
/* Now, clear the best_path cache that may have been created */
|
||||
bp_clear_cachepath();
|
||||
|
||||
getrusage(RUSAGE_SELF, &rus2);
|
||||
logerror("done assembling paths %g user %g system",
|
||||
rus2.ru_utime.tv_sec + rus2.ru_utime.tv_usec / 1e6
|
||||
|
@ -110,6 +124,7 @@ finish_sects(int etu)
|
|||
|
||||
}
|
||||
|
||||
#if (defined(USE_PATH_FIND) || defined(TEST_PATH_FIND)) && !defined(DIST_PATH_NO_REUSE)
|
||||
static int
|
||||
distcmp(const void *p, const void *q)
|
||||
{
|
||||
|
@ -126,6 +141,7 @@ distcmp(const void *p, const void *q)
|
|||
return d;
|
||||
return b - a;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
assemble_dist_paths(double *import_cost)
|
||||
|
@ -133,10 +149,17 @@ assemble_dist_paths(double *import_cost)
|
|||
struct sctstr *sp;
|
||||
struct sctstr *dist;
|
||||
int n;
|
||||
#if defined(USE_PATH_FIND) || defined(TEST_PATH_FIND)
|
||||
static int *job;
|
||||
int uid, i;
|
||||
coord dx = 1, dy = 0; /* invalid */
|
||||
|
||||
#ifdef DIST_PATH_NO_REUSE
|
||||
for (uid = 0; NULL != (sp = getsectid(uid)); uid++) {
|
||||
import_cost[uid] = -1;
|
||||
if (sp->sct_dist_x == sp->sct_x && sp->sct_dist_y == sp->sct_y)
|
||||
continue;
|
||||
#else
|
||||
if (!job)
|
||||
job = malloc(WORLD_SZ() * sizeof(*job));
|
||||
|
||||
|
@ -157,18 +180,75 @@ assemble_dist_paths(double *import_cost)
|
|||
|
||||
for (i = 0; i < n; i++) {
|
||||
uid = job[i];
|
||||
#endif /* !DIST_PATH_NO_REUSE */
|
||||
sp = getsectid(uid);
|
||||
dist = getsectp(sp->sct_dist_x, sp->sct_dist_y);
|
||||
if (CANT_HAPPEN(!dist))
|
||||
continue;
|
||||
if (sp->sct_own != dist->sct_own)
|
||||
continue;
|
||||
#ifdef DIST_PATH_NO_REUSE
|
||||
#if DIST_PATH_NO_REUSE == 1
|
||||
import_cost[uid] = path_find(sp->sct_dist_x, sp->sct_dist_y,
|
||||
sp->sct_x, sp->sct_y, dist->sct_own,
|
||||
MOB_MOVE);
|
||||
#else
|
||||
path_find_from(sp->sct_dist_x, sp->sct_dist_y,
|
||||
dist->sct_own, MOB_MOVE);
|
||||
import_cost[uid] = path_find_to(sp->sct_x, sp->sct_y);
|
||||
#endif
|
||||
#else
|
||||
if (sp->sct_dist_x != dx || sp->sct_dist_y != dy) {
|
||||
dx = sp->sct_dist_x;
|
||||
dy = sp->sct_dist_y;
|
||||
path_find_from(dx, dy, dist->sct_own, MOB_MOVE);
|
||||
}
|
||||
import_cost[uid] = path_find_to(sp->sct_x, sp->sct_y);
|
||||
#endif
|
||||
}
|
||||
#endif /* USE_PATH_FIND || TEST_PATH_FIND */
|
||||
#if !defined(USE_PATH_FIND) || defined(TEST_PATH_FIND)
|
||||
char *path;
|
||||
double d;
|
||||
char buf[512];
|
||||
|
||||
for (n = 0; NULL != (sp = getsectid(n)); n++) {
|
||||
#ifdef TEST_PATH_FIND
|
||||
double new_imc = import_cost[n];
|
||||
#endif
|
||||
import_cost[n] = -1;
|
||||
if ((sp->sct_dist_x == sp->sct_x) && (sp->sct_dist_y == sp->sct_y))
|
||||
continue;
|
||||
dist = getsectp(sp->sct_dist_x, sp->sct_dist_y);
|
||||
if (CANT_HAPPEN(!dist))
|
||||
continue;
|
||||
if (sp->sct_own != dist->sct_own)
|
||||
continue;
|
||||
/* Now, get the best distribution path over roads */
|
||||
/* Note we go from the dist center to the sector. This gives
|
||||
us the import path for that sector. */
|
||||
path = BestDistPath(buf, dist, sp, &d);
|
||||
if (path)
|
||||
import_cost[n] = d;
|
||||
#ifdef TEST_PATH_FIND
|
||||
if (fabs(import_cost[n] - new_imc) >= 1e-6) {
|
||||
printf("%d,%d <- %d,%d %d: old %g, new %g, %g off\n",
|
||||
sp->sct_dist_x, sp->sct_dist_y,
|
||||
sp->sct_x, sp->sct_y, MOB_MOVE,
|
||||
import_cost[n], new_imc, import_cost[n] - new_imc);
|
||||
printf("\told: %s\n", path);
|
||||
d = path_find(sp->sct_dist_x, sp->sct_dist_y,
|
||||
sp->sct_x, sp->sct_y, dist->sct_own, MOB_MOVE);
|
||||
assert(d - new_imc < 1e-6);
|
||||
path_find_route(buf, sizeof(buf),
|
||||
sp->sct_dist_x, sp->sct_dist_y,
|
||||
sp->sct_x, sp->sct_y);
|
||||
printf("\tnew: %s\n", buf);
|
||||
}
|
||||
#endif /* TEST_PATH_FIND */
|
||||
}
|
||||
#endif /* !USE_PATH_FIND || TEST_PATH_FIND */
|
||||
#if defined(USE_PATH_FIND) || defined(TEST_PATH_FIND)
|
||||
path_find_print_stats();
|
||||
#endif
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue