MBED PACMAN (uLCD-144-G2)

Overview

This code example is an attempt at a crude recreation of Pacman for the uLCD-144-G2. Be advised. This is not yet a fully functional rendering of the Pacman game, but is close to serving as a high quality (for the given processing power) single level copy. Figure 1 shows a snapshot of the game at start-up.

https://lh6.googleusercontent.com/-Xqwmoad-1Ys/UzClHCINyZI/AAAAAAAAAWM/DL2j3naCyy4/w401-h534-no/IMG_20140324_173533.jpg

Figure 1: mbed Pacman at start-up


Setup

This particular setup required few hardware components. A 1113_0 Mini Joystick Sensor from Phidgets Inc. served as the controller for the game. Figure 2 below provides a snapshot of the setup, while Table 1 and Table 2 provide the pin-outs for the joystick and LCD.

https://lh4.googleusercontent.com/-4T83OhK2yOs/UzDU0qXXyjI/AAAAAAAAAW0/yx_v2_FzUB8/w769-h577-no/IMG_20140324_205908.jpg

Figure 2: Hardware setup

http://www.phidgets.com/wiki/images/5/51/1113.jpg

Figure 3: 1113_0 mini joystick sensor(2)

mbeduLCD CableuLCD Header
VU5V5V
GNDGNDGND
Tx(p28)TxRx
Rx(p27)RxTx
p29ResetReset

Table 1: mbed to LCD connections(1)

Joystickmbed
RedVOUT
BlackGND
WhiteAnalog In

Table 2: Joystick to mbed connections

The joystick sensor is essentially a 2-dimensional potentiometer creating a voltage divider on 2 axes. Each divider is supplied 3.3V from the mbed to create two nearly full scale analog inputs. The four required control signals for Pacman, move left, right, up, and down, were achieved by setting two threshold levels for each analog input. However, the resistance of the x and y directions of the joystick are not entirely independent. For instance, moving the stick to the right, which decreases the resistance in x, also decreases the resistance in y to a lesser degree. To combat this, the threshold values were set near the end of their respective ranges to avoid giving an unintended input. On a similar note, given the two analog inputs, the x-direction (move left and right) was given a higher priority in the program since it was read in and acted on first. This was done simply to avoid adding the complexity of comparing the two inputs due to the already delayed animation, which will be discussed further. The reading of the joystick inputs is demonstrated in the following code:

void checkMOVE(void)
{
    if(jsx > .9)
    {
        CHECKclearRIGHT();
        PACmoveRIGHT();
    }
    else if(jsx < .1)
    {
        CHECKclearLEFT();
        PACmoveLEFT();
    }
    if(jsy > .9)
    {
        CHECKclearUP();
        PACmoveUP();
    }
    else if(jsy < .1)
    {
        CHECKclearDOWN();
        PACmoveDOWN();
    }
}

Demos

Note the resolution of the game characters is severely limited by the recording device used. The clarity on the LCD is far superior to what is indicated in the videos. Also, note the ghost in the second video will only move back and forth on a patrol of the inner loop with no Pacman present. The third video is one particular instance of the LCD locking up. The behavior of one or both characters can depend on the timing of this lock-up. In this instance, it seems the Pacman position information seems to have been locked or kept from the ghost thread, since the ghost moves directly past Pacman but does not give chase. Other instances of the presence of both characters have ended with the ghost tracking the Pacman down.


Troubleshooting and Future Work

Of course it is possible and likely there is some error in my code causing the LCD to lock up with the introduction of multiple animated characters. However, I believe the catastrophic nature of the problem stems from the high processor demands of a game like Pacman. The mbed with the support libraries for the uLCD-144-G2 is not able to simulate 1-dimensional motion without systematic rules checks entirely seemlessly. The numerous movement restrictions on this game's characters can quickly overwhelm the uLCD-144-G2 library's graphics support.

Take the following function for example. One of the more difficult tasks involving moving Pacman is forcing him to stop once reaching a boundary. Without checking for a wall before each move, he could crash through it destroying the game board in his wake.

void CHECKclearRIGHT(void)
{
    clearRIGHT = true;
    for(i=x; i <= x+CLEARANCE; i++)
    {
        lcd_mutex.lock();
        if(uLCD.read_pixel(i,y)==uLCD.read_pixel(4,4))
        {
            clearRIGHT = false;
        }
        lcd_mutex.unlock();
    }
}

This function checks the area immediately to the right within a certain defined clearance to determine if it is all right for Pacman to move one step size to the right. Notice each pixel between Pacman and the defined clearance is being checked since the wall could be 2 pixels away or 8. It is easy to see, running through such a loop before every move can begin to slow the game play. With enough care in laying out the boundaries with the step size in mind, only one pixel may need to be checked before a move. That is one possibility for improving the performance.

Also making matters worse is the (4,4) pixel read at every iteration. I attempted to assign this reading to an integer, and use that variable for all other comparisons. For some reason, that would not work. In fact, when using read_pixel it assigned the same integer value no matter the color of the pixel being read. The only way to distinguish two pixel colors seemed to be to directly read and compare as shown in the code above. However, that, too, would not work in all cases. read_pixel seemed to be unable to distinguish blue and black pixels. Thus the boundary color was necessarily changed to red. Further inquiry into this matter could also lead to some improved performance.

Another source of concern is the volume of computations associated with tracking the "coins" eaten by Pacman as he passes by. After each move, Pacman's position is compared to the entire array of coins, and if a match occurs, that coin is stored as the location in the center of the board (Pacman cannot go there). Once all coins are eaten, a win condition is found to be true, and the game ends. Comparing Pacman's position to 81 coins each time he moves creates an enormous number of computations.

void changeCOINS(void)
{
    for(int m=0; m<81; m++)
    {
        lcd_mutex.lock();
        if(x == coins[m][0] && y == coins[m][1])
        {
            coins[m][0]=64;
            coins[m][1]=64;
        }
        lcd_mutex.unlock();
    }
}

What I haven't mentioned is the ghost and Pacman are controlled by separate threads, but obviously both must access the coins array. The ghost must redraw the coin as it moves over it, but only if it hadn't already been eaten. This leads to the ghost also having to compare its position to 81 coins, but they can only be accessed by one thread at a time. Since this is also true with the large number of uLCD class calls, many of the variables are constantly locked by one process or the other, which is the biggest reason the two characters seem unable to coexist. I think perhaps the only way to make this game functional would be to greatly reduce the resolution of the movements and reduce the number of coins, which may do enough to allow for a ghost character. Alternatively, both characters could be controlled by the same thread, which would be choppy and slow, but might have a better chance of working.


Program

Import programPacman

This program runs through one level of Pacman. Be advised. This game is not fully functional. The ghost character was commented out due to its seeming inability to coexist with the Pacman character using my current code.


References

1. https://mbed.org/users/4180_1/notebook/ulcd-144-g2-128-by-128-color-lcd/

2. http://www.phidgets.com/products.php?product_id=1113


Please log in to post comments.