In response to Jp
Your code is basically what I had in mind (though in broader context I would argue it needs to be part of a global AI 'brain iteration' than in a loop of its own). One thing I might add:

loc.Exit(src) && t.Enter(src)


I haven't messed with DM in a while, but I think this might act up when someone modifies Move() for disallowing certain kinds of movements (you might end up adding a turf that the mob can't reach to poss_turfs). It's probably a better idea to add all turfs that fulfil (dist <= rand_step_limit), and then take care of useless turfs in the next step:

while (poss_turfs.len > 0)
var/turf/t = pick(poss_turfs)
if (Move(t)) break
poss_turfs -= t

In response to Toadfish
Good idea. Prolly worth keeping the Exit() and Enter() checks, though. They will, at absolute minimum, cut out things like dense turfs.
In response to Complex Robot
Complex Robot wrote:
DivineTraveller wrote:
you can loop until you get a result.

That's a very inefficient way to go about it. Having an unknown amount of iterations before it finishes is a very bad design decision, if you ask me. Especially when you can accomplish it with no iterations at all.

I'm not entirely sure what you're talking about, but there's nothing wrong with having an unknown number of iterations. It's random, so we can use probability to figure out how many iterations there will be on average.

If you're talking about selecting a direction to move, there are two ways to do it:

1. Build a list of all valid directions you can move in, making sure that each move wouldn't put you too far from your starting location. Then select a direction randomly and move in that direction.

2. Randomly pick a direction and try to move. If the move would put you too far from your starting point, deny the move and continue looping (pick another random move and try again).

Since the mob is constrained to a square region, the worst case is that you are in a corner* and have a 1/2 chance of picking a valid direction. The expected number of iterations is 2. The first method always checks each of the 4 moves. Even in the worst case you'd do better to pick a random move and take your chances.

* It depends on the value of Max_Move, but this is probably a rare case.
It has its quirks - Why do pick(1,2,3,4) then map each number to a different direction? There are already numbers to represent directions (NORTH, SOUTH, EAST, WEST). You can then use get_step and get_dist so it looks a lot cleaner.

Also, you might want to have the mob make a valid move each tick. If the mob is at the edge of its movement region it could, each iteration, try to keep moving too far away and end up not moving at all.

But aside from that, it's fine. The only thing I'd do differently is I'd create an area on the map for the mob to wander around in. That way you don't have to limit mobs to square areas that are centered around their starting point. When you're making a map or placing mobs you don't need to think "what's this mob's wander range?" to avoid weird placements/positions, you're placing areas to define the range so you can easily see the limits.

Here's how I'd have written something similar to what you posted:
mob
var
turf/home
max_move = 3
New()
..()
home = loc
spawn(1) wander()
proc
wander()
var/d = pick(NORTH,SOUTH,EAST,WEST)
var/turf/t = get_step(src, d)
if(get_dist(t, home) <= max_move)
step(src, d)
spawn(rand(1,2)) wander()
In response to Forum_account
Using areas also has the benefit of elegance:

area/Exit(atom/movable/M)
if (M.isAI())
return FALSE
else return ..()


There are two caveats to this:

- An area would act a little differently (all mobs in an area will have the same 'wander range'), but this should cause no problem in most cases.
- More significantly if areas are used for something else, you can't reuse them. So you'll need to use a different construct.
In response to Kyle_ZX
Not to make this an issue of semantics, but...

"optimization" typically means taking good code and modifying it to run faster. If you call one proc many times from another you might combine them to reduce the overhead of the function call. Maybe you use additional vars instead of a list to avoid extra overhead/cleanup from lists and list operations. It makes your code faster, but often messier.

If you start with bad code (in general, I mean, this isn't an accusation) and make changes that improve it and make it faster, that's not what people typically mean when they say "optimize". As an example, suppose you want to find the instance of /obj/tree that's nearest to the player out of all the trees on their screen, you can do this two ways:

1. Iterate over all instances of /obj/tree in the world and keep track of which is closest to the player.

2. Iterate over all instances of /obj/tree in view() and keep track of which is closest to the player.

The first method is obviously less efficient. If you initially wrote it the first way and later changed to the second way this would improve performance, but this isn't really the kind of optimizations that people talk about when saying that "premature optimization is bad". This isn't optimizing code - you're not taking good code and making it faster, you're taking bad code and making it good.

When writing code you shouldn't focus on performance, but you shouldn't totally ignore it. You should try to write good code - readability is probably most important. That's what the "premature optimization is bad" point boils down to - write good code and if it's bad, you can fix it later. Don't write messy code to avoid performance problems that you aren't even sure exist, you'll just create more problems for yourself.
In response to Toadfish
Toadfish wrote:
- An area would act a little differently (all mobs in an area will have the same 'wander range'), but this should cause no problem in most cases.

I know what you mean, but strictly speaking this wouldn't always be true. If the area contains disconnected sets of turfs you can use a single /area instance that describes many movement ranges.

####
#m##  ###
####  ###
      #m#
      ###

# = area
m = mob


- More significantly if areas are used for something else, you can't reuse them. So you'll need to use a different construct.

Precisely =)

I don't think the library supports the Exit() proc, but it'd still work somehow.
In response to Forum_account
I didn't know that an area could be disconnected like that. That's nice.

There is still the case where you want a certain group of mobs to move closer to the centre of an area (the king's royal guard), while another group of mobs moves about freely (the servants). I'm not sure if you can 'overlay' areas somehow but if you can that would presumably solve all problems*. If it's is impossible, and one still needs this kind of logic in the game, that's all the more reason to use your library though (assuming it supports this).

*Edit: Okay wait, it wouldn't, since 'servants' could enter the 'throne room' and then not be able to Exit() it. I'm not sure if there's an elegant solution here. Perhaps mobs need a var/area/freedom_of_movement_area property that determines which area restricts their movement.
In response to Toadfish
You might be able to do that with areas. If you have each mob remember it's "home area" (the area its initially in) you can probably use the type hierarchy to pull off some of these effects. It might not be easy to keep track of, though.

For example:
area
royal_chambers
throne_room
sleeping_quarters
Enter(mob/m)
if(istype(m))
if(!istype(src, m.home_area))
return 0
return 1

mob
var/home_area
New()
..()
var/area/a = loc:loc
home_area = a.type


A mob placed on a turf inside /area/royal_chambers will wander the throne room (because /area/royal_chambers/throne_room is of type /area/royal_chambers) and the sleeping quarters, but a mob placed in the throne room will stay within the throne room. You are still limited because you can't have two mobs that wander a common area and two non-common areas (one unique to each mob).

Currently regions don't support Enter() and Exit(), but I should have an update ready later today to add that.. Edit: I just posted an update to the region library that adds support for Enter and Exit. Then it'd be as simple as your example with overriding area/Exit().
In response to Forum_account
Quick aside, but what's the difference between doing while() and calling the proc within itself with spawn()?
In response to LordAndrew
LordAndrew wrote:
Quick aside, but what's the difference between doing while() and calling the proc within itself with spawn()?

If you just do while() with a sleep() inside the proc will never finish. Because this is a constructor, something might be waiting for it to finish. If you want to use while(), you'd have to do something like this:

mob
New()
spawn()
while(1)
sleep(1)
// do something


Using spawn() at the end of the proc like I did means less indentation, so I tend to write it that way (especially when writing things out on the forum).
Page: 1 2