Yeah, but that’s actually not too complicated. At least from a technical implementation standpoint. I think the real problem would be in implementing something that wasn’t overly confusing/annoying to the player. But the nuts and bolts are a pretty well-explored problem: that’s how virtually every first-person dungeon crawl game handles player position/movement.
Just for giggles, here’s a toy implementation in TADS3/adv3 to illustrate the concept(s):
#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>
modify playerActionMessages
okayWizTurnDir(dir, rot) {
return('{You/he} turn{s} <<rot>>. {You/he} {are} now
facing <<dir>>. ');
}
wizStepFailed = '{You/he} can\'t go that way. '
cantWizNotWizard = '{You/he} can\'t do that. '
;
class WizTurnAction: IAction
wizTurnStr = nil
wizRotation = nil
execAction() {
if(!gActor.ofKind(WizActor)) {
reportFailure(&cantWizNotWizard);
exit;
}
gActor.wizRotate(wizRotation);
defaultReport(&okayWizTurnDir, gActor.wizDirName(), wizTurnStr);
}
;
DefineAction(WizLeft, WizTurnAction)
wizTurnStr = 'left'
wizRotation = -1
;
VerbRule(WizLeft) 'left': WizLeftAction verbPhrase = 'turn/turning';
VerbRule(WizRight) 'right': WizRightAction verbPhrase = 'turn/turning';
DefineAction(WizRight, WizTurnAction)
wizTurnStr = 'right'
wizRotation = 1
;
DefineIAction(WizForward)
execAction() {
local c, dir, dst, rm;
if(!gActor.ofKind(WizActor)) {
reportFailure(&cantWizNotWizard);
exit;
}
dir = gActor.wizDirName();
rm = gActor.getOutermostRoom();
Direction.allDirections.forEach(function(d) {
if(d.name != dir) return;
c = rm.getTravelConnector(d, gActor);
if(!c || !c.isConnectorApparent(rm, gActor))
return;
dst = c.getDestination(rm, gActor);
});
if(dst) {
replaceAction(TravelVia, dst);
} else {
reportFailure(&wizStepFailed);
}
}
;
VerbRule(WizForward) 'step': WizForwardAction
verbPhrase = 'step/stepping';
wizCfg: object
_dirName = static [ 'north', 'east', 'south', 'west' ]
rotate(dir, rot) {
dir += rot;
while(dir > 4) dir -= 4;
while(dir < 1) dir += 4;
return(dir);
}
dirName(idx) { return(_dirName[idx]); }
;
#define gWizRotate(dir, rot) wizCfg.rotate(dir, rot)
#define gWizDirName(v) wizCfg.dirName(v)
class WizActor: Actor
wizDir = 1 // start pointing north, for some reason
wizDirName() { return(gWizDirName(wizDir)); }
wizRotate(rot) { wizDir = gWizRotate(wizDir, rot); }
;
startRoom: Room 'Void'
"This is a featureless void. The south room is south of here. "
south = southRoom
;
+me: WizActor;
southRoom: Room 'South Room'
"This is the south room. The void is north of here. "
north = startRoom
;
versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;
We define a couple new intransitive verbs, >LEFT
, >RIGHT
, and >STEP
(>FORWARD
already exists in adv3 with a different meaning, but that’s something that could be sorted out in a real game). We also define an WizActor
class for actors that are allowed to move this way.
Thrilling transcript:
Void
This is a featureless void. The south room is south of here.
>left
You turn left. You are now facing west.
>right
You turn right. You are now facing north.
>right
You turn right. You are now facing east.
>right
You turn right. You are now facing south.
>step
South Room
This is the south room. The void is north of here.
Orientation is just a numeric value between 1 and 4, inclusive, WizActor.wizDir
. Right is (arbitrarily) positive rotation and Left is negative, and the new actor facing is the old one plus the rotation mod 4 (with the caveat that TADS3 idiosyncratically indexes from one instead of zero, so we’re a) not using an actual % operator and b) our value is actually dir = ((dir + rot) % 4) + 1
…if you care about that sort of thing). This all takes substantially more effort to explain than implement.
Then our >STEP
command just figures out what direction the actor is pointing in and then either attempts to go that way or reports the failure.
This implementation assumes a four point compass (so left of north is west, not northwest), but extending it to include other directions would be straightforward. There’s also no instrumentation…in a real game you’d want a verb to repeat the current facing, maybe put it in the status line, provide custom travel reports, and so on.
But it should illustrate the basic mechanics, if that’s what you’re interested in.