How Frightened Ghosts Decide Where to Go

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Pac-Man is a completely deterministic game. As  shown in my last video on the game, all of the   ghosts' movements are based off of where Pac-Man  currently is in the maze. Therefore, with the   right knowledge, you can predict exactly where  any of the ghosts will move at any time. Or can   you? When Pac-Man eats a power pellet, the ghosts  become frightened and seem to move in a random,   unpredictable pattern. This is the only time  a random number generator is used in the game:   to determine which direction a ghost will turn  at an intersection in the maze while frightened.   While still deterministic, it is the  only non-predictable element of Pac-Man.   This video will go under a deep analysis of  the game's RNG function, and how the ghosts   tend to behave in this situation. We'll find  that frightened ghosts do tend to gravitate   towards a certain section of the maze after  all. The random number generator, or RNG,   is responsible for, well, generating random  numbers. The processor running the game's   code doesn't have a little coin to flip or a dice  to roll to get any source of randomness, so this   feature has to be programmed via software. And  it turns out that writing a program or a function   to come up with numbers randomly is quite  difficult--you could argue it is impossible.   Writing code to generate random numbers is  essentially like creating a list of instructions   on how to calculate a certain value. In that case,  it isn't truly random--it's still deterministic.   However, the end result looks random enough  that it can be treated as if it really were   random--pseudo-random if you will. Pac-Man has  a very basic RNG function, but with a twist.   There is a 16-bit value in memory that is treated  as the RNG index. This is set to zero at the start   of the game, and at the start of each level. This  index is used as a seed value to generate the next   index, which overwrites the old one. For example,  the very first index generated is the number 1.   The RNG function takes the number 0 as  its input, and outputs the number 1.   Then, the next time a random number is needed,  it uses the number 1 as its input. In this case,   if 1 is its input, the output will be the number  6. Like I said, the rule is very simple. We take   the input index, multiply it by 5, and add 1.  That's it! Well, and also modulo by 8192. Which   just means, if the result is greater than 8192,  divide it by 8192 and then take the remainder.   For example, the eighth RNG index is 7544. If  we multiply that by 5 and add 1, we get 37721.   Divide that by 8192 to get 4 with a remainder  of 4953. So the ninth RNG index is 4953.   This function does an alright job at generating  random numbers. There are times where it's quite   obvious what the pattern is (especially at the  start with 0, 1, 6, 31), but most of the time it's   fine. What's important though is the distribution  of the numbers. Are all numbers equally likely to   be generated? It turns out that, yes, they are.  At least all numbers less than 8192 of course.   This function will run through all 8192  numbers between 0 and 8191 before it repeats.   The last number in the pattern being 4915. This  is a good quality for an RNG function to have,   since it means that there is no bias towards  certain outputs. At least at a single,   random access. (There may be strong correlations  if you want to generate more than one random   number at a time, but that's not going to  be a problem for us for multiple reasons.)   However, notice that I have been calling these  RNG indices, and not the RNG outputs themselves.   That's because this index is not the true  output of the function. It's just a helper   value that is used to create the output. This  index is used as an offset into the game's code   to grab a single byte--that's the output.  It's not an index into a table of any sorts   either. The first 8192 bytes of the assembly  code of Pac-Man is used as the RNG output.   Most of the bytes here are machine code that  represents the assembly instructions that make   up the various functions in the game's code--none  of which have anything to do with random numbers.   Here's an analogy: suppose you were told  to come up with a random English word.   It would be like using the same function for the  index here, but then using that index to look up   a word in your favorite book. If your input index  was 6189, you'd calculate your output index to be   6370, so you'd find the 6370th word in the book.  While this method of looking up a byte definitely   removes the correlation between successive random  numbers, it impacts the uniformity of the output.   I mean, imagine how much more likely you are  to randomly pick the word "the" from your book!   Here is a chart that displays how many of  each of the 256 different bytes there are   in the first 8 kilobytes of the game's code. As  you can see, this is hilariously non-uniform.   For a uniform distribution, each number  should appear 32 times. However, zero   appears over 600 times. That's because  there's some empty spaces in the code,   which are just filled with nothing but zeros. The  number 77 also appears a lot, even more than zero.   This is because this corresponds to the byte  $4D, and the memory region from $4D00 to $4DFF   is where most of the game's main variables are  held. So each instruction that wants to read   or write to any of those variables is going  to make the byte $4D show up in the code.   Some numbers just don't show up at all in the  first 8 kilobytes, like 65, 141, 146, and 186.   Now, this may look bad, but fortunately  there's a catch that almost saves this.   The only reason the RNG function is used is to  determine what direction a frightened ghost moves   at an intersection. Well, there's only four  possible options: up, down, left, or right.   This can be represented with only  two bits. The actual mapping of   these bits to directions is 00 for right,  01 for down, 10 for left, and 11 for up.   Because of this, the only part of the output of  this RNG function that's really important is the   bottom two bits! So let's regroup all of these  outputs based only on their bottom two bits.   Okay that's a little better, I guess? I mean the  last distribution set a really low bar to beat.   You would expect a uniform distribution to output  each of the 4 options 2048 times out of 8192.   While the 00 bits corresponding to "right"  are almost there, the others are just way off.   The bits 11 corresponding to "up" are only  1338, which is just 16.3% of the total.   This is one of two flaws of the frightened ghosts'  behaviors--the RNG function that governs their   movement is not uniform. The directions  "left" and "down" are heavily biased for,   while "up" is very heavily biased against.  So you'd think this would mean the ghosts   would just favor the bottom left corner of  the maze then, right? Well not so fast...   Real quick, in case you are interested,  here is the RNG function itself.   Only 13 assembly instructions, that's how you know  its simple. Here is where the old index is loaded   into the CPU. The index gets multiplied by five  here. And incremented by one. Here's the modulo   by 8192--done by logically ANDing the upper 8 bits  of the index with $1F--essentially truncating the   index to 13 bits. That index is used to grab a  byte from the game's ROM, and then the new index   is stored to memory, overwriting the old one. It's  unfortunate that this function is found at $2A23   in the game's code, outside of the reach of the  RNG index which is limited to $1FFF. Which means   the bytes that make up the RNG function can't  be themselves the output of the RNG function.   So the frightened ghosts use the RNG output  to determine which direction to move at an   intersection. What if they can't physically move  in that direction? Suppose a ghost was at this   intersection, but the RNG told the ghost to move  up. There's a wall there, so what happens now?   Instead of generating another random number,  the ghost will just try the next direction   in a clockwise order. In this case, the  ghost will instead try to move right.   But oops, ghosts can't turn around 180 degrees  either! This ghost approached the intersection   from the right, so it can't choose to go right  here. This direction is no good, and it tries   the next direction. It tries moving down next,  which is actually a valid direction to move.   This results in a very biased decision. If the  ghost were to roll an RNG value that corresponds   to "up", "right", or "down", they all correspond  to going "down". The only way a ghost can go left   at this intersection if they are coming in from  the right is if "left" is chosen from the start.   According to the chart we made earlier, this  would be a 29.9% chance. This leaves a whopping   70.1% chance for the ghost to choose to move  down. This is the second flaw of the frightened   ghosts' behaviors--the allocation of random  numbers to directions is not uniform either.   We can use this information to create a table  of probabilities for each intersection type.   Since the RNG is biased itself, none of  these situations are symmetrical at all.   First, it's worth pointing  something else out right now.   Frightened ghosts don't call the RNG at just  the intersection tiles in the maze. They call   the RNG every time the move from one tile  to another, even well inside the tunnels.   Since the ghosts are blocked by walls on two  sides, and they can't turn around 180 degrees,   there is only one valid direction to move.  This is actually somewhat notable, because   by calling the RNG function even in these trivial  cases, it changes the RNG index value. This can   help with shuffling up the index to prevent  any noticeable patterns from emerging (say,   by observing which direction ghosts are moving  to predict which directions will be chosen next).   But anyway. There are six types of tunnel  tiles--two straight tunnels and four corners.   For each of these tiles, a ghost can enter it  from two different directions, which results in 12   possibilities. These 12 cases can be grouped into  four sets: one where "right" is the only valid   option, one for "down", "left", and "up" as well.  Trivially, even though the distribution of random   numbers results in this sort of distribution  of directions, the only valid direction is   straight forward, and a ghost has a 100% chance of  continuing in the direction it was already moving.   Where it gets interesting is the 3-way  intersections. There are four types of   3-way intersections, and a ghost could enter  from one of the 3 sides, which results in 12   more different cases. These can again be split  into sets corresponding to which two directions   are actually valid. There are six of these sets,  and this is what the likeliness of a ghost turning   in each direction is. Now, the two sets that are  split between two opposite directions aren't too   bad. This is because each direction picks up the  weight of one of its neighboring directions. The   most fair case is when a ghost has to pick between  moving up or down--a random direction of "left" or   "up" results in a 46.3% chance of going up, while  a random direction of "right" or "down" gives   a 53.7% of going down. The other four sets for  picking two adjacent directions are ridiculously   lopsided though. They are all basically 3-to-1  odds for picking one direction over another.   The worst offender is left versus up--a frightened  ghost only has a 16.3% of choosing to move up!   Finally we have the 4-way intersection,  of which a ghost can enter from all four   different directions. Here's what those  distributions look like. In all four cases,   it is about twice as likely that the ghost will  turn left from its perspective in the maze. This   is because the only invalid direction  is doing a 180 and backtracking--this   gets rotated 90 degrees clockwise, which  corresponds to turning to the ghosts' left.   Now how can we use this information to figure out  where the ghosts are more likely to go? I know   this isn't a very practical question, since ghosts  don't get around very much while frightened, and   in the later levels they don't become frightened  at all. But I think it could be a fun exercise.   The best way to analyze this problem  in my opinion is with a Markov chain.   If you don't know what a Markov  chain is, here is a quick overview.   Suppose you have some sort of process that can  exist in one of several states. We can represent   these states as a set of vertices in a graph. So  here we have three different states: A, B, and C.   And in this process, at every step in  time, the current state can change.   The probability of changing into a different state  is determined strictly by the current state. Let's   say if you are currently in state A, you have a  50/50 chance of switching to state B or state C.   We can represent these state transitions  as weighted, directed edges in this graph.   Similarly, if you are in state B, you may  have a 30% chance of changing to state C,   60% chance of changing to state A, and  even a 10% chance of staying in state B.   We represent this with a self-edge from B to B.  And so on. This is a Markov chain. We can sort of   simulate an instance of this process here by, say,  starting in state A, and then moving from state   to state based on the probabilities denoted by the  edges coming out of the node of the current state.   Just do this like a million times and then  tally up how many times each state was visited,   and you can find the average time spent in each  state. With a really large graph, this can take   exponentially longer to converge on a meaningful  value though, so there is a better way to do this.   We can start with a "mass" of 100 on state A,  and then divvy up the mass proportional to the   outgoing edges on each vertex. In this case,  100 gets split up 50/50 to both states B and C.   This leaves state A empty with zero, since states  B and C were previously empty. Then on the next   step, State A has 0 mass so it doesn't give out  anything. State B gives 60% of its 50 to state A,   30% to state C, and keeps 10% to itself. While  State C gives 30% to state A, 50% to state B,   and keeps 20% to itself. With this step, now state  A has a mass of 45, state B has 30, and state C   has 25. Keep stepping through time like this and  eventually the numbers will converge on a steady   state fairly quickly (as long as the chain is a  special kind of chain known as "ergodic" which   isn't important to explain in this video, but you  can look up more about it if you're interested).   The fun thing though, is that, if the Markov  chain is ergodic, then this is the steady state   of the chain; it doesn't matter what the initial  conditions were. We started with a mass of 100   on state A, but we could start with a mass of 100  on state B or state C, and we would arrive to the   same steady state. So what we can do, is build  a Markov chain that represents each intersection   in the Pac-Man maze as a vertex in the graph,  and the edges will be the paths between them.   The weights of the edges will be the  biased probabilities that I showed earlier.   Then we can plop down a mass of 100 frightened  ghosts anywhere in the graph and iterate the   whole thing over and over until we reach a steady  state. This will essentially give us a heat map   of where the frightened ghosts like to hang out  in the maze. So, let's do it! It shouldn't be too   hard. Since the tunnels between the intersections  are somewhat superfluous, I'll omit them at first.   I'll add them back in later once we understand  what's going on and we know everything is working   properly. First, we can make one vertex in  our graph for each intersection in the maze.   We'll need two edges for each path  between them, one for each direction.   Then all we need to do is fill in all the edge  weights with the RNG direction probabilities,   right? Well, not so fast. Let's  look at a single intersection.   This vertex has three edges coming out of it,  representing the three directions a ghost can   move out of this intersection. But we know  that a ghost cannot turn around 180 degrees.   When a ghost enters a 3-way intersection, it  really only has two choices to go, not three.   This is because we haven't captured the entire  state of a ghost with our vertices. Not only do   they need to represent the position of the ghosts  within the maze, they also have to encode which   direction they are traveling too. This means  that each intersection in the maze needs a   set of vertices corresponding to it--one for each  direction the ghost can approach the intersection.   Three-way intersections will have three  vertices and 4-way intersections will have   four vertices each. I'll put little  arrows inside to make it clearer.   Now we can hook up the edges properly. If a  ghost is approaching this intersection from   the right, this will be represent by this  vertex, since the ghost is moving left.   If approaching from the left, this vertex, and  approaching upwards, this one. Now the outgoing   edges. If the ghost is approaching upwards,  it can go either left or right. From the left,   it can continue moving right, or go down. And from  the right, it can go down, or move on to the left.   Now we can apply the edge weights with  the percentages calculated earlier.   A ghost entering this intersection from the right  can go left with 30% chance, and down with a 70%   chance. And so on, you get the idea. You can also  see that having these intersections split into   multiple vertices allows the different approach  directions to affect the direction probabilities.   Here the ghost has a 70% chance of going down  if entering from the right, but if entering   from the left, it only has a 29% chance. We just  have to break down each intersection like this.   This is a lot more complicated, but this is due  to the restriction that the ghosts just can't   go backwards. Now, before we calculate the steady  state, we need to add the straight paths back in.   We can't just ignore them completely because the  distance between every intersection isn't equal.   A ghost will spend a longer time in a longer  path, like those at the bottom of the maze,   simply because they take longer to  traverse. So what we really need to   do is represent each tile in the maze as a  set of vertices, not just each intersection.   This is pretty trivial to do, since a  ghost can only move forward in a tunnel.   Each tunnel tile will have two vertices associated  with it, one each for what direction the ghost   is moving. Then, we just connect each of the  directions together with a probability of 1.   Essentially what this does, is forwards all of  the ghosts that turned a certain direction at   one intersection to the next intersection  without allowing them to turn around.   It also delays them from reaching the next  intersection proportional to the length of the   path between the two intersections. But now we can  finally simulate the frightened ghosts' behavior   with this behemoth of a Markov chain. For the  sake of this video, I am going to simplify what   this graph looks like. Since we are ultimately  interested in where the ghosts are in the maze,   we don't really need to know what direction  they are moving. Therefore, I am going to   take all of the vertices that correspond to each  tile and group them up into these meta-vertices.   The mass of these meta-vertices is just the  combined mass of the vertices inside of it,   which represents how many ghosts are currently  on this tile, regardless of what direction they   are facing. And I'm not showing the edges of the  graph anymore because it's pretty intuitive that   adjacent vertices in the maze connect to each  other. Now, instead of plopping down 100 ghosts   in a single tile, I'm going to put 50 ghosts  each on the two tiles right by the ghost house.   For each tile, 25 will be facing left, and  25 will be facing right. If you know why this   might be important, or want to give your best  guess, post about it in the comments! Anyway,   here we go! The ghosts slowly disperse throughout  the maze. Since I picked 100 ghosts, the mass for   each tile represents the percentage of the time  you will find a frightened ghost on said tile.   You're more likely to find a ghost at an  intersection as opposed to a tunnel just   because there are more ways to approach  those tiles, but you might be able to   see that there is another pattern appearing. The  distribution appears not to be symmetric at all!   I know at the start I said to hold on, the ghosts  might not just favor the bottom left of the maze,   but it turns out that is exactly the case.  In fact, a frightened ghost is six times   more likely to be found in the bottom left as  opposed to the top right. That is, if given a   large enough amount of time, which is way longer  than you would ever experience in-game, but hey.   Now, what is the root cause for this bottom-left  bias? Remember, there were two flaws to the   ghosts' behavior. With this Markov chain, we can  tinker with the probabilities and separate and   isolate those two different properties  and see what kind of affect they have.   First, lets start with fixing both problems.  Here I'll set the probabilities of choosing   a direction to be a perfect 50/50  split at all 3-way intersections,   and a perfect 1/3 chance for each direction at  a 4-way intersection. Simulating this maze, we   see that the distribution of ghosts is pretty much  uniform. Like I mentioned before, ghosts would be   more likely to be at an intersection just due to  having more ways to approach the tile. This also   shows that one of these two flaws is definitely  the culprit. Let's fix the RNG function first.   Here I'll change the probability of a ghost  rolling one of the four directions to be a perfect   25% chance each. The ghost will still pivot that  option 90 degrees if it would move backwards or   turn into a wall, meaning that some intersections  are still split 75 to 25. It looks like the ghosts   still prefer the bottom left corner, but not  by as much. This sort of makes sense. Yes,   the method of pivoting the direction to take  is heavily biased to turning left, but it is   rotationally symmetric. Each biased movement  rule is part of a set of four rules that are 90   degrees apart from one another, and they should  all cancel out over time. The only reason they   don't completely cancel out is because the maze  itself isn't 90 degrees rotationally symmetric.   This produces small biases in certain locations  just due to the shape of the maze itself.   Now let's see if we isolate the other variable.  In this case, I kept the biased RNG function,   but removed the rule about pivoting the direction.  If the ghost rolls a direction that would make   it hit a wall or turn around backwards, it  rolls again until it picks a valid direction.   Aha, here's the culprit! In fact, this ends with  an even worse distribution than the unmodified   version. The pivoting mechanic allows for a chance  to "break free" from the bias of the RNG function.   For example, at this intersection, since  a ghost approaching from the left can't   go back to the left, normally, if it rolls  a "left", it will end up going up instead.   Even though "up" has the worst odds on  its own, the fact that left is blocked   practically triples the probability a ghost will  turn up here. Without this boost, a ghost only   has a 23% chance instead of a 46% chance of  going up. This makes it very unlikely that a   ghost will hang out at the top of the maze for any  substantial amount of time in these circumstances.   Finally, I just want to make sure, is this  all accurate? Markov chains are cool and all,   but was this a proper way to represent the  behaviors of the frightened ghosts? Well, there's   only one way to find out. Instead of simulating  in a sandbox, let's try running the actual game!   Only a minimal amount of tinkering with the game  will make this easy to test. Just force the ghosts   to always be frightened, and move Pac-Man out  of bounds so that he can't eat any of them.   Then, I'll just let the ghosts do their thing and  sample their positions every few minutes or so.   Speed up the game up as fast as I can  make it go, and have it run for hours.   After recording thousands of data points of  where the ghosts are, we can plot a heat map   out of it. Here's what my results were. And hey,  that's shockingly similar! It turns out math is   useful after all. But is it really? The only way  we can realize this bias is by making the ghosts   frightened for way longer than they were intended  to be. In reality, the main factor that determines   where a frightened ghost is in the maze, is where  the ghost is when Pac-Man eats the power pellet.   I think the more meaningful rule to come away with  this is: frightened ghosts prefer to turn left   from their point of view. The only exceptions  to this are when they are moving to the right   into a wall, where they will slightly prefer  turning to their right to go down, and when   they are moving downwards into a wall, where they  will slightly prefer turning right to go left.   And then ultimately, none of this information  matters at all in the later levels, since   ghosts overcome their fears and no longer become  frightened even after a power pellet is eaten.   Thank you for watching! This episode of  Retro Game Mechanics Explained was made   possible by supporters of the channel via  Patreon. If you would like to help support   the video-making process, you can become a  patron for as little as one dollar a month.   Patrons get early access to videos as  well as behind the scenes content such   as creator commentary. I appreciate  every little bit I get, so thank you!
Info
Channel: Retro Game Mechanics Explained
Views: 122,466
Rating: undefined out of 5
Keywords: video, game, programming, code, glitch, trick, explain, description, hack, tas, pacman, ghost, ai, maze, random, rng, assembly, graph, markov, chain
Id: eFP0_rkjwlY
Channel Id: undefined
Length: 29min 46sec (1786 seconds)
Published: Mon May 29 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.