git.dumitru.net nush / master src / pathing.c
master

Tree @master (Download .tar.gz)

pathing.c @masterraw · history · blame

/* -*- c-basic-offset: 8 -*- */
/* This file contains A*, Dijkstra, reading from/writing out 2D grids of Lua
   values, and priority queues */

#include <stdio.h>
#include <stdlib.h>
#include "nush.h"


/* Dijkstra/A* node */
typedef struct {
	disttype f;   /* sorted by */
	disttype g;
	unsigned short x, y;  /* Count from 1! */
} Node;


/* Returns true if Qelem at index idx1 is <= in order to idx2 */
static int lesseq(Node lhs, Node rhs) {
	return lhs.f <= rhs.f;
}

void error(const char *msg) {
	log_printf("%s", msg);
	luaL_error(L, msg);
}


/****************************** Priority Queue *******************************/
/* Binary heap priority queue to retrieve minimum element */


/* Element of the priority queue (passed around by value) */
typedef Node Qelem;

typedef struct { 
	Qelem *data;
	int size;
	int allocated;
} PQueue;

PQueue *PQueue_new()
{
	PQueue *pq = malloc(sizeof(PQueue));
	pq->size = 0;
	pq->allocated = 48;
	pq->data = malloc(sizeof(Qelem) * pq->allocated);
	return pq;
}

void PQueue_free(PQueue *pq)
{
	free(pq->data);
}

int PQueue_size(PQueue *pq)
{
	return pq->size;
}

#define LEFT_CHILD(idx) (2*idx+1)
#define PARENT(idx) ((idx-1)/2)

/* Return a least element */
Qelem PQueue_pop(PQueue *pq)
{
	if (!pq->size)
		error("pop from empty queue");

	Qelem ret = pq->data[0];

	/* Bubble up the hole from root until finding a spot to put to_insert */
	int hole = 0;
	Qelem to_insert = pq->data[--pq->size];  /* pop off end */
	while (LEFT_CHILD(hole) < pq->size)
	{
		/* Find the smallest child */
		int left_child = LEFT_CHILD(hole), right_child = left_child + 1;
		int smallest = left_child;
		if (right_child < pq->size && lesseq(pq->data[right_child], pq->data[left_child]))
			smallest = right_child;

		if (lesseq(to_insert, pq->data[smallest]))
			break;  /* smaller than both children */
		pq->data[hole] = pq->data[smallest];
		hole = smallest;
	}
	pq->data[hole] = to_insert;

	return ret;
}

void PQueue_push(PQueue *pq, Qelem element)
{
	if (pq->size == pq->allocated)
	{
		pq->allocated *= 2;
		pq->data = realloc(pq->data, sizeof(Qelem) * pq->allocated);
	}

	/* Bubble down the value until its parent isn't larger */
	int hole = pq->size++;  /* where to place */
	while (hole > 0)
	{
		int parent = PARENT(hole);
		if (lesseq(pq->data[parent], element))
			break;
		pq->data[hole] = pq->data[parent];
		hole = parent;
	}
	pq->data[hole] = element;
}


/********************************** LuaMap ***********************************/
/* Lazily query a lua list-of-lists data structure encoding a map,
   caching previous results. */


#define LUAMAP_UNCACHED_TILE -424242.

/* Create a LuaMap that isn't linked to a Lua value, just filled with an
   initial value. */
LuaMap *LuaMap_new(int w, int h, disttype initval)
{
	LuaMap *map = malloc(sizeof(LuaMap));
	map->tiles_index = 0;   /* marks this LuaMap as not tied to a table */
	map->attr_index = 0;    /* unused */
	map->default_value = 0; /* unused */
	map->w = w;
	map->h = h;
	map->tiles = malloc(sizeof(disttype) * (w + 1) * (h + 1));
	int i;
	for (i = 0; i < (w+1)*(h+1); i++)
		map->tiles[i] = initval;
	return map;
}

/* Create a LuaMap that caches the values in a lua 2D grid (list-of-lists).
   tiles_index:   Lua stack index of the grid.
   attr_index:    0 if it's a grid of raw values, or the Lua stack index of a
                  string to use as a key.
   default_value: Value assigned to 'nil' tiles.
*/
LuaMap *LuaMap_from_table(int tiles_index, int attr_index, int w, int h, disttype default_value)
{
	/* Tiles start uncached only if there's a table to read from */
	LuaMap *ret = LuaMap_new(w, h, LUAMAP_UNCACHED_TILE);
	ret->tiles_index = tiles_index;
	ret->attr_index = attr_index;
	ret->default_value = default_value;
	return ret;
}

void LuaMap_free(LuaMap *map)
{
	free(map->tiles);
}

disttype LuaMap_read(LuaMap *map, int x, int y)
{
	int to_pop = 0;
	disttype *tile = &map->tiles[(x - 1) + (y - 1) * map->w];
	if (*tile != LUAMAP_UNCACHED_TILE)
		return *tile;
	if (!map->tiles_index)
		error("LuaMap_read() called on a LuaMap without a table data source");

	/* Read the value from the map */
	lua_rawgeti(L, map->tiles_index, x);    /* push tiles[x] */
	lua_rawgeti(L, -1, y);                  /* push tiles[x][y] */
	to_pop = 2;
	if (map->attr_index)                    /* get an attribute of tiles[x][y] */
	{
		to_pop++;
		lua_pushvalue(L, map->attr_index);
		lua_gettable(L, -2);            /* pop key and push cost */
	}
	/* Convert to value */
	int type = lua_type(L, -1);
	if (type == LUA_TBOOLEAN)               /* tiles[x][y].key */
	{
		if (lua_toboolean(L, -1))
			*tile = 999999;         /* true: impassable */
		else
			*tile = 1;              /* cost = 1 for cost maps */
	}
	else if (type == LUA_TNIL)
		*tile = map->default_value;
	else
		*tile = lua_tonumber(L, -1);
	lua_pop(L, to_pop);

	return *tile;
}

void LuaMap_write(LuaMap *map, int x, int y, disttype value)
{
	map->tiles[(x - 1) + (y - 1) * map->w] = value;
}

/* Push this map of onto the lua stack as a 2D list-of-lists-of-ints.
   Tiles which haven't been set with LuaMap_write() become 'false' */
void LuaMap_push(LuaMap *map)
{
	lua_createtable(L, map->w, 0);

	int x, y;
	for (x = 1; x <= map->w; x++)
	{
		lua_createtable(L, map->h, 0);
		for (y = 1; y <= map->h; y++)
		{
			disttype value = map->tiles[(x - 1) + (y - 1) * map->w];
			if (value == LUAMAP_UNCACHED_TILE)
				lua_pushboolean(L, 0);
			else
				lua_pushnumber(L, value);
			lua_rawseti(L, -2, y);
		}
		lua_rawseti(L, -2, x);
	}
}


/********************************* Dijkstra map ******************************/


/* compute_dijkstra_map internal */
static void dijvisit(PQueue *pq, LuaMap *map, LuaMap *dists, Node parent, int xoff, int yoff)
{
	int x = parent.x + xoff, y = parent.y + yoff;
	if (x < 1 || x > map->w || y < 1 || y > map->h)
		return;

	disttype cost = parent.f + LuaMap_read(map, x, y);
	/* Give a slight penalty to diagonal moves, to prevent unnecessary zig-zagging */
	if (xoff && yoff)
		cost += 0.001;

	/* Check against best known cost both before and after pushing/popping from PQ */
	if (cost < LuaMap_read(dists, x, y))
	{
		Node node;
		node.f = cost;
		node.x = x; node.y = y;
		PQueue_push(pq, node);
	}
}

/* Starting from roots pushed into pq, update distmap with minimal distances from roots.
   costmap: A map giving the cost to step onto each tile.
   distmap: Initially filled with either a large constant (maxcost) if unvisited,
            or a lower value if a goal node.
 */
static void compute_dijkstra(PQueue *pq, LuaMap *costmap, LuaMap *distmap)
{
	while (PQueue_size(pq))
	{
		Node node = PQueue_pop(pq);
		/* Skip if not better than known */
		if (node.f >= LuaMap_read(distmap, node.x, node.y))
			continue;
		LuaMap_write(distmap, node.x, node.y, node.f);

		int xoff, yoff;
		for (xoff = -1; xoff <= 1; xoff++)
		{
			for (yoff = -1; yoff <= 1; yoff++)
			{
				if (xoff || yoff)
					dijvisit(pq, costmap, distmap, node, xoff, yoff);
			}
		}
	}
}

/* Computes a LuaMap giving the weighted shortest-path distance from (x,y) to
   every tile up to maxcost cost away. Unreached tiles have the value maxcost. */
LuaMap *single_source_dijkstra_map(LuaMap *costmap, int x, int y, disttype maxcost)
{
	PQueue *pq = PQueue_new();
	LuaMap *distmap = LuaMap_new(costmap->w, costmap->h, maxcost);

	/* Start node. We store the distance in Node.f */
	Node node;
	node.f = 0;
	node.x = x; node.y = y;
	PQueue_push(pq, node);

	compute_dijkstra(pq, costmap, distmap);
	PQueue_free(pq);
	return distmap;
}

/* Computes for every 'tile' the minimum over all goals of
   min(maxcost, distance(goal, tile) + cost of goal tile). */
void multiple_source_dijkstra_map(LuaMap *costmap, LuaMap *distmap, disttype maxcost)
{
	PQueue *pq = PQueue_new();

	/* Find all sources in distmap and push them onto the queue.
	   Possible optimisation would be to use lua_next to only visit
	   tiles that aren't nil. */
	int x, y;
	for (x = 1; x <= distmap->w; x++)
	{
		for (y = 1; y <= distmap->h; y++)
		{
			disttype value = LuaMap_read(distmap, x, y);
			if (value < maxcost)
			{
				Node node;
				node.f = value;
				node.x = x; node.y = y;
				PQueue_push(pq, node);
			}
			/* Write maxcost to this tile even if it's a goal, so
			   that when it's popped off the queue it isn't
			   immediately disregarded. */
			LuaMap_write(distmap, x, y, maxcost);
		}
	}

	log_printf("multiple_source_dijkstra_map: found and pushed %d sources", pq->size);
	compute_dijkstra(pq, costmap, distmap);
	PQueue_free(pq);
	return;
}

/*********************************** Testing *********************************/

/*
int main() {
	srand(time(NULL));

	PQueue *pq = PQueue_new();
	Node n;

	int i, j;
	for (i = 0; i < 1000; i++) {
		n.value = rand() % 90;
		PQueue_push(pq, n);
	}

	while (PQueue_size(pq)) {
		n = PQueue_pop(pq);
		printf("%d ", n.value);
	}
	puts("\n");

	PQueue_free(pq);
	return 0;
}
*/