NPC Random Movement

Excluding the existing “transit systems” in Deadline, Suspect, Suspended etc what would be a good way to code randomised movement into an NPC?

Thanks
Adam

The way I might try to tackle this is to have basically an interrupt routine which perhaps either looks at a global variable (for when to move the npc. or if it is time to move them) and maybe have it randomly select from a table of all of the places that npc moves to.

<GLOBAL NPC-ROOMS <TABLE
    ROOM1
    ROOM2
    ROOM3
    . . .
    ROOM-N>>

Then, it would only be a matter of invoking the RANDOM routine (with the maximum index into this table) and take the room at that index, from this table. Then Viola! You have an NPC which randomly moves between these points. Now there are other ways of course, if it is a small map, you could have this interrupt routine just manually move the npc, in a more hardcoded way, to some specific locations. (For example, maybe the doctor only goes between his office and his examination room?)

Of course, these are merely suggestions, I’m positive that there could be several ways one could achieve this.

1 Like

Seems logical enough! Thank you! :+1:

I’m a beginner, but I used this to code a ghost that teleports randomly around a specific region of the game world, and it seems to work well for my purposes.

A ghost is a kind of person. The phantom is a ghost in the Dining Hall.
The First Floor is a region. The Dining Hall is a room in the First Floor.

A room is either haunted or unhaunted. A room is usually unhaunted. 

Definition: A room is haunted if it has a ghost in it.

When play begins:
	the phantom teleports in four turns from now.
	
At the time when the phantom teleports:
	if Phantom is in a room (called the current phantom space):
		let the next phantom space be a random not haunted room that is in the First Floor;
		if the phantom is visible, say "The phantom disappears in a flash!";
		now the phantom is in the next phantom space;
		if the phantom is visible, say "The phantom appears in a flash!";
	the phantom teleports in four turns from now.

Now this is a specific case, as it’s a creature that doesn’t need to move into intermediate room - but what I like about doing this is it makes it easy to code other NPCs’ varying behavior depending on whether or not the phantom is present. It’s also easy to add additional reactions to the Phantom’s entrances and exits within the teleport event.

2 Likes

I see the logic… but I think that’s Inform 7? I’m using ZIL :slightly_smiling_face: Thanks though for taking the time to reply :+1:

1 Like

Have you used random NPC movement much before?

In my small attempts, I’ve found I lose touch with how the game is supposed to play. I find it very difficult to test if it’s triggering with the right frequency to be effective.

Right now I have an NPC who patrols and offers hints when he meets the player. And I’m struggling to get that right from a narrative point of view, even when this interaction is more predictable than a random one.

Ah, my bad :: still learning how everything works here.

2 Likes

If you look under the thread title, you can see that this is the “ZIL” subsection of the “Authoring” section of the forum. That’s the easiest way to know which language/engine people are discussing in the thread. Some board sections have additional tags for clarification if they have more than one language that may be relevant to the engine (like Twine, for instance).

It’s easy for new users to assume that this board is all about Inform 7, but it actually caters to quite a few different engines and languages.

2 Likes

This is also not in ZIL (sorry), but the code is simple enough that I think you should be able to see the logic behind it. I was going to just explain how I did it, but I figured the code would be clearer. The code is in JavaScript and has a couple user functions not shown, but hopefully it all makes sense.

There are probably better ways, but this is a simple method that I cobbled together to avoid the feeling that the character was basically teleporting all over the map. It felt really jarring. This way feels like they’re naturally walking about the house.

JavaScript
function moveChar(char) {
	// only move characters half of the time
	if (!percentCheck(50)) return;
	
	// assign exits to locations
	var rooms = {
		"foyer": ["2F_hallway", "east_hallway"], 
		"2F_hallway": ["foyer"],
		"east_hallway": ["kitchen", "dining_room", "bar", "living_room", "back_hallway"],
		"kitchen": ["east_hallway", "dining_room"],
		"dining_room": ["east_hallway", "kitchen"],
		"bar": ["east_hallway"],
		"living_room": ["east_hallway"],
		"back_hallway": ["east_hallway", "theater", "pool", "veranda"],
		"theater": ["back_hallway"],
		"pool": ["back_hallway"],
		"veranda": ["back_hallway", "back_yard"],
		"back_yard": ["veranda"]
	};

	// track where they were last
	var last_location = char.location; 
	
	// move character to random location accessible from their current location
	char.location = randomItem(rooms[char.location]);

	// print messages if you're able to see them enter or leave your location
	if (last_location === player_location) write(char.name + " wanders off.");
	else if (char.location === player_location) write(char.name + " wanders into the room.");
}
1 Like

If you want the NPC to wander around the map by picking random exits, instead of teleporting to random locations across the map, you can use MAP-DIRECTIONS to loop through the direction properties in the NPC’s location. Loop once to count the number of useful exits (probably limited to UEXITs), then generate a random number in that range and loop again to find the n’th exit.

2 Likes

Thank you! :slightly_smiling_face::+1:

I might as well post here too that I wrote an example for Adam the other day, showing how it might be done in ZIL if the game is small enough (he provided a map of what he had in mind).

I did try to incorporate a few different ideas. The NPC follows a non-random patrol route, but it can be affected by both global and local things. If you turn on a radio, he will walk towards it to turn it off, and there is a door that he will always close if he finds it open. (Which of course means there has to be an exception if the radio is behind that door. Isn’t conflicting goals fun?)

There is the gigantic caveat that I’m no ZIL expert by any stretch of imagination, but if someone wants to play around with it, improve it, host it elsewhere, etc. feel free.

3 Likes

You can use what’s been done in some Infocom games. The programmers used the PICK-ONE routine with a table of rooms to choose a room.

The original version of PICK-ONE would just pick a random entry out of a table (this was mentioned before). Subsequent calls could pick the same room. The newer version will only pick an entry if it has not been picked already. Once all the entries are picked, then all options are fair game again. So you can repeatedly call PICK-ONE and get “randomly” chose rooms without repeats.

If you need to walk a random path, then Jesse’s method is best. If you know the lowest numbered exit property (LOW-DIRECTION in some games), then you can have a routine that randomly picks a number out of all the directions (LOW-DIRECTION to 31). If that entry is zero or not the type of exit you want (like a door), then you can just have the routine pick another random number in that range.

FYI: New version of PICK-ONE From Zork I -

<ROUTINE PICK-ONE (FROB "AUX" (L <GET .FROB 0>) (CNT <GET .FROB 1>) RND MSG RFROB)
	 <SET L <- .L 1>>
	 <SET FROB <REST .FROB 2>>
	 <SET RFROB <REST .FROB <* .CNT 2>>>
	 <SET RND <RANDOM <- .L .CNT>>>
	 <SET MSG <GET .RFROB .RND>>
	 <PUT .RFROB .RND <GET .RFROB 1>>
	 <PUT .RFROB 1 .MSG>
	 <SET CNT <+ .CNT 1>>
	 <COND (<==? .CNT .L> <SET CNT 0>)>
	 <PUT .FROB 0 .CNT>
	 .MSG>
1 Like

Do you mind explaining further how to use MAP-DIRECTIONS for this purpose? Unsure how to use it.

1 Like

Below is a description for MAP-DIRECTIONS with a small example on how it could be used (from ZILF Reference Guide):

+++++++++++++++++++++++++++++++++++++++++++++

MAP-DIRECTIONS

<MAP-DIRECTIONS (name pt room) [(END expressions ...)] expressions ...>

Loop over all directions in a room. For each iteration name is assigned the current direction and pt is the room the direction leads to.

For each iteration the expressions are evaluated and, if supplied, the (END expressions …) is evaluated last after all other iterations.

Example:

	<DIRECTIONS NORTH SOUTH EAST WEST>

	<OBJECT CENTER (DESC "center room")  
    		(NORTH TO N-ROOM) 
    		(WEST TO W-ROOM)>

	<OBJECT N-ROOM (DESC "north room")>

	<OBJECT W-ROOM (DESC "west room")>

	<ROUTINE TEST-MAP-DIRECTIONS ()
    		<TELL "You're in the " D ,CENTER>
		<TELL CR "Obvious exits:" CR>
    		<MAP-DIRECTIONS (D P ,CENTER)
        		(END <TELL "Room description done." CR>)
        		<COND (<EQUAL? .D ,P?NORTH> <TELL "    North">)
              		 (<EQUAL? .D ,P?SOUTH> <TELL "    South">)
              		 (<EQUAL? .D ,P?EAST> <TELL "    East">)
              		 (<EQUAL? .D ,P?WEST> <TELL "    West">)>
        		<VERSION?
            		(ZIP <TELL " to the " D <GETB .P ,REXIT> CR>)
            		(ELSE <TELL " to the " D <GET .P ,REXIT> CR>)>>>
1 Like

I’m trying to have a play with it, and the code is looking like this.

<MAP-DIRECTIONS (D PT ,HERE)
        (END <RTRUE>)

        <COND (<=? .D ,P?NORTH> <TELL "North, which leads " D <GET .PT ,UEXIT> CR>)>>

However this complains about HERE not being a local, but when I have it has a reference, HERE instead of ,HERE then it complains about not having a .. LVAL or GVAL. What’s going on?

Are you using the zillib parser (have it included) and Is HERE initialized to a value?

Below is a small test-program with a moving NPC and MAP-DIRECTIONS:

"MAP-DIRECTIONS"

<VERSION XZIP>
<CONSTANT RELEASEID 1>

<CONSTANT GAME-BANNER "|A small test with a NPC and moving it using MAP-DIRECTIONS.||">

<ROOM U-LEFT
    (DESC "Upper Left")
    (EAST TO U-CENTER)
    (SE TO M-CENTER)
    (SOUTH TO M-LEFT)
    (FLAGS LIGHTBIT)>
    
<ROOM U-CENTER
    (DESC "Upper Center")
    (WEST TO U-LEFT)
    (SW TO M-LEFT)
    (SOUTH TO M-CENTER)
    (SE TO M-RIGHT)
    (EAST TO U-RIGHT)
    (FLAGS LIGHTBIT)>
    
<ROOM U-RIGHT
    (DESC "Upper Right")
    (WEST TO U-CENTER)
    (SW TO M-CENTER)
    (SOUTH TO M-RIGHT)
    (FLAGS LIGHTBIT)>
    
<ROOM M-LEFT
    (DESC "Middle Left")
    (NORTH TO U-LEFT)
    (NE TO U-CENTER)
    (EAST TO M-CENTER)
    (SE TO L-CENTER)
    (SOUTH TO L-LEFT)
    (FLAGS LIGHTBIT)>
    
<ROOM M-CENTER
    (DESC "Middle Center")
    (NORTH TO U-CENTER)
    (NE TO U-RIGHT)
    (EAST TO M-RIGHT)
    (SE TO L-RIGHT)
    (SOUTH TO L-CENTER)
    (SW TO L-LEFT)
    (WEST TO M-LEFT)
    (NW TO U-LEFT)
    (FLAGS LIGHTBIT)>
    
<ROOM M-RIGHT
    (DESC "Middle Right")
    (NORTH TO U-RIGHT)
    (NW TO U-CENTER)
    (WEST TO M-CENTER)
    (SW TO L-CENTER)
    (SOUTH TO L-RIGHT)
    (FLAGS LIGHTBIT)>
    
<ROOM L-LEFT
    (DESC "Lower Left")
    (EAST TO L-CENTER)
    (NE TO M-CENTER)
    (NORTH TO M-LEFT)
    (FLAGS LIGHTBIT)>
    
<ROOM L-CENTER
    (DESC "Lower Center")
    (WEST TO L-LEFT)
    (NW TO M-LEFT)
    (NORTH TO M-CENTER)
    (NE TO M-RIGHT)
    (EAST TO L-RIGHT)
    (FLAGS LIGHTBIT)>

<ROOM L-RIGHT
    (DESC "Lower Right")
    (WEST TO L-CENTER)
    (NW TO M-CENTER)
    (NORTH TO M-RIGHT)
    (FLAGS LIGHTBIT)>
    
<ROUTINE GO () 
    <QUEUE I-TURTLE -1>
    <SETG HERE ,M-CENTER>
    <MOVE ,PLAYER ,HERE>
    <INIT-STATUS-LINE>
    <V-VERSION> <CRLF>
    <V-LOOK>
    <MAIN-LOOP>>

<OBJECT TURTLE
 (IN U-LEFT)
 (DESC "turtle")>
 
<INSERT-FILE "zillib/parser">

<ROUTINE I-TURTLE ("AUX" (N 0) (TURTLE-LOC <LOC ,TURTLE>) EXIT-NUM)
    ;"Count # of exits at NPCs current location"
    <MAP-DIRECTIONS (DIR DEST .TURTLE-LOC) <INC .N>>
    ;"Pick random number"
    <SET EXIT-NUM <RANDOM .N>>
    ;"Move turtle in picked direction"
    <MAP-DIRECTIONS (DIR DEST .TURTLE-LOC) 
        <DEC .EXIT-NUM>
        <COND (<0? .EXIT-NUM>
            <TELL "The turtle moves ">
            <TELL-DIR .DIR> 
            <TELL ". ">
            <SET TURTLE-LOC <GET .DEST ,REXIT>>
            <MOVE ,TURTLE .TURTLE-LOC>)>>
    <TELL "The turtle is now in the \"" D .TURTLE-LOC "\" room." CR>
    <COND (<=? ,HERE .TURTLE-LOC> <JIGS-UP "||Success! You catched the turtle!">)>>
    
<ROUTINE TELL-DIR (DIR)
    <COND (<=? .DIR P?NORTH> <TELL "north">)
          (<=? .DIR P?EAST> <TELL "east">)
          (<=? .DIR P?SOUTH> <TELL "south">)
          (<=? .DIR P?WEST> <TELL "west">)
          (<=? .DIR P?NE> <TELL "northeast">)
          (<=? .DIR P?NW> <TELL "northwest">)
          (<=? .DIR P?SE> <TELL "southeast">)
          (<=? .DIR P?SW> <TELL "southwest">)>>
1 Like

,HERE should work. I modified your code a little and expanded it to a full program below:

<VERSION XZIP>

<CONSTANT GAME-BANNER ""> 

<INSERT-FILE "zillib/parser">

<ROOM BEDROOM
    (DESC "Bedroom")
    (NORTH TO BATHROOM)
	(FLAGS LIGHTBIT)>
    
<ROOM BATHROOM
    (DESC "Bathroom")
    (SOUTH TO BEDROOM)
	(FLAGS LIGHTBIT)>
	
<ROUTINE GO ()
	<SETG HERE BEDROOM>
	<MOVE ,PLAYER ,HERE>
	<INIT-STATUS-LINE>
	<V-LOOK>
	<PRINT-EXITS>
	<MAIN-LOOP>>

<ROUTINE PRINT-EXITS ()
	<MAP-DIRECTIONS (D PT ,HERE)
		(END <RTRUE>)
        <COND (<=? .D ,P?NORTH> <TELL "North, which leads " D <GET .PT ,REXIT> CR>)>>>

EDIT: Changed your use of ,UEXIT to ,REXIT. The different types of exits are identified by the length of the property. An UEXIT has a length of 2 (1 for version 1-3) but if you use this to try to retrieve the room value you’ll end up with the wrong value. REXIT instead points to the position inside the property where the room number is stored (the same for all different types of exits). ,REXIT is equal to 0 so <GET .PT ,REXIT> could equally be <GET .PT 0>. One other thing to consider is that in version 1-3 the room number is a byte and in later versions the room number is a 16-bit word, this means that in the earlier version you retrieve the room number with <GETB .PT ,REXIT> instead.

1 Like

You can use <GET/B .PT ,REXIT>, which will turn into either GETB or GET depending on the Z-machine version.

3 Likes