bl791's picture
download
raw
24.6 kB
/* Copyright (c) 2001 by Kevin Forchione. All Rights Reserved. */
/*
* TADS ADV.T/STD.T LIBRARY EXTENSION
* TRACKACTOR.T
* Version 3.0
*
* THE TRAVEL GRAPH
*
* The travel graph is a multi-graph of weighted directed
* edges, that may contain self-loops. To simplify the automation
* process only travel properties that resolve to property types
* of _object_ are used to build adjacent vertex and adjacent edge
* lists.
*
* Multiple edges between a given pair of vertices are flattened
* out by resolving the traversal between the two vertices as
* passable if any of the edges is found to be passable, and
* impassable otherwise. This allows for simple paths to be
* computed by dijkstra's shortest paths algorithm.
*
* Weighting of edges between vertices is based upon the
* actor's ability to pass all of the obstacles that may
* lie along the given edge. Impassable edges are given a
* weight of INFINITY, while edges for which passage is
* possible are give a weight of 1. The edge weights are
* maintained through all path computations for a given
* station (see below), and re-adjudicated only when the
* next station is being considered.
*
* If a passable edge has been found for the two vertices
* then the adjacent vertex itself is examined to determine
* if it is accessible to the actor. Inaccessible vertices
* are temporarily removed from path computations. After a
* new path has been computed the bias against the
* inaccessible vertex is removed.
*
* STATIONS
*
* TrackActor class allows the author to set stations as
* points of travelling preference along the graph. Stations
* are used to form the destinations of simple paths computed
* by dijkstra's algorithm.
*
* When a path to a station has been found to be impassable,
* the trackActorDaemon() will remove the impassable vertex
* or edge from its new path computation and attempt to
* traverse this new path. This strategy is continued until
* either a path to the station has been successfully
* traversed; or all possible paths to the station are
* exhausted. If all paths have been exhausted the station
* is abandonned and we proceed to calculate a path from
* the actor's location to the next station in the list.
*
* TRAVERSAL STYLE
*
* In this way, stations are run in sequence as they appear
* in the actor's station list until the end of the list is
* reached. At that point the traversal style used to set
* the station list will determine what happens next. The
* pattern of this sequence can be any of three traversal
* styles:
*
* pathTraversal: the station list is processed once only.
* cycleTraversal: the station list is processed once, returning
* the actor to the first station when the end has
* been reached.
* continuousTraversal: the station list is processed as a continuous
* loop from beginning to end.
*
* EXAMPLE
*
* setStation( [loc1, loc2, loc3], continuousTraversal );
*
* The above statement tells TrackActor to set its station
* list to [loc1, loc2, loc3] and its traversal style to
* continuousTraversal. The actor's destination will be
* set to loc1and a path computed from the actor's location
* to loc1.
*
* The actor will then follow the computed path (track list)
* until it completes it or finds an edge or vertex it cannot
* pass. If this is the case then a new path to its destination
* is computed, eliminating the obstructing edge or vertex from
* the calculation.
*
* After the actor has arrived at loc1 (or it has been
* determined that this no paths to the destination exist)
* a new path starting from the actor's location to loc2
* will be computed; the process repeated for loc3 and
* the whole cycle starts over again by the actor returning
* from loc3 to loc1.
*
*----------------------------------------------------------------------
* REQUIREMENTS
*
* + HTML TADS 2.2.6 or later
* + Should be #included after ADV.T and STD.T
* + Requires dijkstra.t
*
*----------------------------------------------------------------------
* IMPORTANT LIBRARY INTERFACE AND MODIFICATION
*
* + Overrides preinit()
*
*----------------------------------------------------------------------
* COPYRIGHT NOTICE
*
* You may modify and use this file in any way you want, provided that
* if you redistribute modified copies of this file in source form, the
* copies must include the original copyright notice (including this
* paragraph), and must be clearly marked as modified from the original
* version.
*
*------------------------------------------------------------------------------
* REVISION HISTORY
*
* 28-Jan-01: Creation.
* 06-Feb-01: Major restructuring!
*/
#ifndef _TRACK_ACTOR_T_
#define _TRACK_ACTOR_T_
#include <dijkstra.t>
#pragma C+
sayDirName: function;
randomElement: function;
TrackActor: object
isActive = nil
traversalStyle = nil
destination = nil
movementCount = 0
stationPos = 1
trackPos = 1
stationList = []
trackList = []
failedVertexList = []
failedEdgeList = []
failedStationList = []
/*
* Stations are vertices within the game world multi-graph which
* form the destinations for the tracks the actor must traverse.
* This method sets the actor in motion by setting its station
* list, calculating the track list for the first station, and
* notifying the trackActorDaemon.
*/
setStation( vertexList, type ) = {
self.stationList = [];
self.traversalStyle = type;
if (length( vertexList ) == 1
&& self.traversalStyle != pathTraversal )
self.stationList += self.location;
self.stationList += vertexList;
self.stationPos = 1;
self.stationListCycles = 0;
povStack.push(self);
self.isActive = true;
self.failedVertexList = [];
self.failedEdgeList = [];
self.setPath( self.location, self.stationList[ self.stationPos ] );
povStack.pop;
}
/*
* Creates the track list for the destination. Track lists are
* computed using dijkstra's shortest paths algorithm.
*/
setPath( start, dest ) = {
self.trackPos = 1;
self.destination = dest;
self.trackList = Path.shortestPathVertices(start, dest);
}
trackActorDaemon = {
local cur, nxt, edge, povSave;
if ( !self.isActive ) return;
/* set the point of view to this actor */
povStack.push(self);
/* Reset movement count */
self.movementCount = 0;
while( self.hasMore )
{
/* Get the actor's current and next locations */
cur = self.location;
nxt = self.trackList[ self.trackPos ];
/*
* Retrieve a passable edge for the current and next
* vertices.
*/
edge = self.getEdge( cur, nxt );
if ( edge != nil )
{
if ( self.canMoveToVertex( nxt ) )
{
/*
* We remove the nxt vertice from the failed
* station list, if it has been added from an
* earlier iteration.
*/
if ( nxt == self.destination )
self.failedStationList -= nxt;
self.traverseEdge( edge );
break;
}
/*
* If this vertex is temporarily blocked to the actor we
* need to recalculate the path, eliminating this vertex
* from our calculations -- temporarily.
*/
if ( nxt != self.destination )
{
self.failedVertexList += nxt;
self.setPath( cur, self.destination );
self.handleInvalidVertex( nxt );
continue;
}
}
/*
* The actor's current location is the next location in the
* track list. We simply increment the track position and start
* the process over. This is checked after edge and vertice
* validation to allow for graph self-loops.
*/
if ( cur == nxt )
continue;
/*
* This route is blocked to the actor. We need to recalculate
* the path, eliminating the failed edge list from our
* calculations.
*/
self.setPath( cur, self.destination );
if ( nxt == self.destination
&& length( self.trackList ) == 0 )
{
local tmp = [ nxt ];
self.failedStationList += (tmp - self.failedStationList);
}
self.handleNoValidEdges( cur, nxt );
}
/* Reset the pov */
povStack.pop;
}
hasMore = {
++self.trackPos;
++self.movementCount;
if ( length(self.stationList - self.failedStationList) == 0)
{
/*
* Dijkstra didn't compute a path for any of the stations,
* so shut the actor down.
*/
self.handleNoValidStations;
return nil;
}
/*
* When we reach the end of the track list we check to see if
* we need to stop or go to the next station.
*/
if ( length(self.trackList) < self.trackPos )
{
/* Ask traversal style is we should stop moving */
if ( self.traversalStyle.trackListCompleted(self,
self.stationListCycles) )
return nil;
/* increment the station position */
++self.stationPos;
/*
* If we've reached the end of the station list we need to
* determine if we should stop or recycle through the list.
*/
if ( length(self.stationList) < self.stationPos )
{
/* Ask traversal style is we should stop moving */
if ( self.traversalStyle.stationListCompleted(self) )
return nil;
/*
* Increment the number of times we've been thru the
* station cycle list.
*/
++self.stationListCycles;
/* set the station position back to 1 */
self.stationPos = 1;
}
/* Reset the failed vertex and edge lists */
self.failedVertexList = [];
self.failedEdgeList = [];
/* Compute the track list for the new station */
self.setPath( self.location, self.stationList[
self.stationPos ] );
}
return true;
}
getEdge( cur, nxt ) = {
local i, len, edge, obsList;
local edgeList = [], obstacleEdgeList = [];
/*
* Create an edge list containing each edge object that matches
* the actor's current and next locations. Build a separate
* list from the edge list that contains all edges that have
* obstacles.
*/
len = length( cur.adjacentEdgeList );
for ( i = 1; i <= len; ++i )
{
edge = cur.adjacentEdgeList[ i ];
if ( edge.vertexFrom == cur && edge.vertexTo == nxt )
{
edgeList += edge;
if ( length( edge.obstacleList ) > 0 )
{
obstacleEdgeList += edge;
}
}
}
/* remove all edges with obstacles from the edge list */
edgeList -= obstacleEdgeList;
/* Set edge to nil */
edge = nil;
if ( length( edgeList ) > 0 )
{
/* we have at least 1 passable edge */
edge = randomElement( edgeList );
return edge;
}
/*
* All edges have obstacles. Check each edge to see if the
* actor can pass all of the obstacles for the given edge. If
* the actor can pass all of the obstacles then travel to the
* first obstacle.
*/
len = length( obstacleEdgeList );
for ( i = 1; i <= len; ++i )
{
obsList = obstacleEdgeList[ i ].obstacleList;
if ( self.canPassEdge( obsList ) )
{
edge = obstacleEdgeList[ i ];
return edge;
}
}
/* Add the obstacle edge list to the failed edge list */
self.failedEdgeList += obstacleEdgeList;
return nil;
}
/*
* Test whether the actor can pass all of the obstacles for a
* given edge. Returns true if the actor can; otherwise returns
* nil.
*/
canPassEdge( obstacleList ) = {
local i, len, o;
len = length( obstacleList );
for ( i = 1; i <= len; ++i )
{
o = obstacleList[i];
if ( !self.canPassObstacle( o ) )
{
/* we can't pass the obstacle */
return nil;
}
}
/* No obstacle has stopped passage */
return true;
}
/*
* Test whether the actor can pass the specified obstacles for
* a given edge. The method should return true if passage is
* allowed; nil otherwise. The default simply tests to see
* if the obstacle is closed and locked or not auto open.
*/
canPassObstacle( o ) = {
if ( !o.isopen
&& ( o.islocked || o.noAutoOpen ) )
{
/* we can't pass the obstacle */
return nil;
}
/* The obstacle has not stopped passage */
return true;
}
/*
* This method is used to determine if the vertex
* is, for some reason, off-limits to the actor.
*/
canMoveToVertex( v ) = {
return true;
}
isFirstPass = { return (self.movementCount == 1); }
// move to a new location, notifying player of coming and going
traverseEdge( edge ) = {
local old_loc_visible;
local new_loc_visible;
local room, obs;
obs = car(edge.obstacleList);
if ( obs )
room = obs;
else
room = edge.vertexTo;
/* if it's an obstacle, travel to the obstacle instead */
while( room != nil && room.isobstacle )
room = room.destination;
/* do nothing if going nowhere */
if ( room == nil )
return;
/* note whether the actor was previously visible */
old_loc_visible = (self.location != nil
&& parserGetMe().isVisible(self.location));
/* note whether the actor will be visible in the new location */
new_loc_visible = parserGetMe().isVisible(room);
/*
* if I'm leaving the player's location, and I was previously
* visible, and I'm not going to be visible after the move, and
* the player's location isn't dark, show the "leaving" message
*/
if (parserGetMe().location != nil
&& parserGetMe().location.islit
&& old_loc_visible
&& !new_loc_visible)
self.sayLeaving( edge.dirPtr );
/* move to my new location */
self.moveInto( room );
/*
* if I'm visible to the player at the new location, and I
* wasn't previously visible, and it's not dark, show the
* "arriving" message
*/
if (parserGetMe().location != nil
&& parserGetMe().location.islit
&& new_loc_visible
&& !old_loc_visible)
self.sayArriving( edge.dirPtr );
}
// sayLeaving and sayArriving announce the actor's departure and arrival
// in the same room as the player.
sayLeaving( dirPtr ) = {
self.location.dispParagraph;
"\^<<self.thedesc>> leaves";
switch( dirPtr )
{
case &up:
case &down:
case &in:
case &out:
" the area. ";
break;
default:
" to the <<sayDirName( dirPtr )>>. ";
}
}
sayArriving( dirPtr ) = {
self.location.dispParagraph;
"\^<<self.thedesc>> enters ";
switch( dirPtr )
{
case &up:
case &down:
case &in:
case &out:
" the area. ";
break;
default:
" from the <<sayDirName( dirPtr )>>. ";
}
}
// Hooks for special transition handling
/*
* This method is called when no valid edges have been found and
* after a new path has been calculated weighting the edges between
* cur and nxt at INFINITY.
*/
handleNoValidEdges( cur, nxt ) = {}
/*
* This method is called when the next vertex is found to be
* impassable to the actor and after a new path has been calculated
* by removing the vertex from dijkstra's considerations. By
* default we remove the vertex from the failed vertex list.
*/
handleInvalidVertex( nxt ) = {
self.failedVertexList -= nxt;
}
/*
* This method is called when all of the stations in the actor's
* station list have been added to the failed station list. This
* means that the actor has failed to find paths for all of the
* stations. By default we stop the actor's attempts to move.
*/
handleNoValidStations = {
self.isActive = nil;
}
/*
* This method is called when the traversal style is cycleTraversal
* and the cycle has been completed. By default we stop the actor's
* attempts to move.
*/
handleCompletedCycle = {
self.isActive = nil;
}
/*
* This method is called when the traversal style is pathTraversal
* and the path has been completed. By default we stop the actor's
* attempts to move.
*/
handleCompletedPath = {
self.isActive = nil;
}
;
/*
* TraversalStyle indicates whether movement is to stop
* at the points when the end of the track list or station
* list has been reached.
*/
class TraversalStyle: object
trackListCompleted(actor, stationListCycles) = { return nil; }
stationListCompleted(actor) = { return nil; }
;
pathTraversal: TraversalStyle
/* movement stops when we reach the end of the station list */
stationListCompleted(actor) = {
actor.handleCompletedPath;
return true;
}
;
cycleTraversal: TraversalStyle
/* movement stops when we've cycled around the station list once */
trackListCompleted(actor, stationListCycles) = {
if ( stationListCycles > 0 )
{
actor.handleCompletedCycle;
return true;
}
return nil;
}
;
continuousTraversal: TraversalStyle
;
class Edge: object
vertexFrom = nil
vertexTo = nil
dirPtr = nil
obstacleList = []
instantiate( vf, vt, d, o ) = {
local e = new Edge;
e.vertexFrom = vf;
e.vertexTo = vt;
e.dirPtr = d;
e.obstacleList = o;
return e;
}
;
/*
* Say the direction name for the direction pointer.
*/
sayDirName: function( dirPtr )
{
switch( dirPtr )
{
case &north:
"north";
break;
case &ne:
"northeast";
break;
case &nw:
"northwest";
break;
case &south:
"south";
break;
case &se:
"southeast";
break;
case &sw:
"southwest";
break;
case &east:
"east";
break;
case &west:
"west";
break;
case &up:
"up";
break;
case &down:
"down";
break;
case &in:
"in";
break;
case &out:
"out";
break;
}
}
/*
* Returns an element of the list selected at random.
*/
randomElement: function( lst ) {
local r, len = length( lst );
if ( len == 1 )
r = 1;
else
r = _rand( len );
return lst[ r ];
}
povStack: object
povList = []
currentPov = nil
push( val ) = {
local tmpList = self.povList;
self.currentPov = val;
self.povList = [ val ] + tmpList;
}
pop = {
local cp, val;
val = car(self.povList);
if ( val == nil ) {
val = parserGetMe();
self.currentPov = val;
}
else
{
self.povList = cdr(self.povList);
cp = car(self.povList);
if ( cp == nil )
self.currentPov = parserGetMe();
else
self.currentPov = cp;
}
return val;
}
;
/*
* Modify Path to weight edges that are found to be unpassable to the
* track actor. If we find a corresponding edge for v & w we return a
* value that will compute to INFINITY for comparison of the edge
* between the two vertices. If we don't find an edge in the track
* actor's failed edge list then we return 1.
*/
modify Path
computeEdge( v, w ) = {
local i, len, e;
local edgeList = [];
edgeList = povStack.currentPov.failedEdgeList;
len = length( edgeList );
for ( i = 1; i <= len; ++i )
{
e = edgeList[ i ];
if ( v.vertex == e.vertexFrom && w.vertex == e.vertexTo )
return INFINITY;
}
return 1;
}
;
modify room
adjacentVertexList = []
adjacentEdgeList = []
setAdjacentVertices = {
local i, dPtr, w, len, e, obsList;
local dirList = [ &north, &south, &east, &west, &ne, &nw, &se,
&sw, &up, &down, &in, &out ];
len = length( dirList );
for ( i = 1; i <= len; ++i )
{
if ( proptype( self, dirList[ i ] ) == DTY_OBJECT )
{
dPtr = dirList[i];
w = self.(dPtr);
/* build a list of all obstacles */
obsList = [];
while( w != nil && isclass(w, obstacle) )
{
obsList += w;
w = w.doordest;
}
e = Edge.instantiate(self, w, dPtr, obsList);
self.adjacentEdgeList += e;
/* Ensure that the adjacent vertex list is unique */
w = [ w ];
self.adjacentVertexList += (w - self.adjacentVertexList);
}
}
}
getAdjacentVertices = {
return (self.adjacentVertexList -
povStack.currentPov.failedVertexList);
}
;
initTravelGraph: function {
local o;
o = firstobj(room);
while( o != nil )
{
o.setAdjacentVertices;
o = nextobj( o, room );
}
povStack.push(parserGetMe());
}
replace preinit: function
{
local o;
global.lamplist = [];
o = firstobj();
while(o != nil)
{
if (o.islamp)
global.lamplist = global.lamplist + o;
o = nextobj(o);
}
initSearch();
initTravelGraph();
}
#pragma C-
#endif /* _TRACK_ACTOR_T_ */

Xet Storage Details

Size:
24.6 kB
·
Xet hash:
851edff9b94e929d950abe09d8b3305d4197aa531c5ac9d88802554f52dfd009

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.