Sign in to follow this  
EqualityAssignment

Pacman clone completed!!!

Recommended Posts

So I have completed my pacman clone! I'll try not to flood this post with code (I don't think anyone wants to read that XD), but I've got a few questions and a few snippets that I was hoping someone could comment on. The code is written in python + pygame. Here's the game-loop (it's not the first function that get's called, there's a text based menu thing, but it's pretty close).
def game(levelname):
    game_on = True
    ghosts_vulnerable = False
    vulnerable_time = 1000
    dots_eaten = 0
    eaten_fruits = list()
    score = 0
    try:
        (screen, input_queue, MVC_triplets, dots_to_eat, walls,
         revive_zone, on_screen_font, score_area, level_width,
         level_height, fruit_start_x, fruit_start_y, fruit_alive_time,
         fruits, fruit_area) = initialize(levelname)
        while (game_on):
            # Input.
            quit_game = handle_input(input_queue)
            # Update.
            update(input_queue, MVC_triplets)
            # Apply game logic ...
            (game_over, ghosts_vulnerable, vulnerable_time, dots_eaten,
             deleted_objects, eaten_fruits,
             score) = game_logic(MVC_triplets, dots_to_eat, walls,
                                 revive_zone, ghosts_vulnerable,
                                 vulnerable_time, dots_eaten,
                                 input_queue, level_width,
                                 level_height, fruits,
                                 fruit_start_x, fruit_start_y,
                                 fruit_alive_time, eaten_fruits,
                                 score)
            if (quit_game or game_over):
                game_on = False                
            # Render.
            render(dots_eaten, screen, MVC_triplets, dots_to_eat, walls,
                   revive_zone, on_screen_font, score_area, deleted_objects,
                   eaten_fruits, fruit_area, score)
            # Limit framerate.
            pygame.time.wait(5)
    except (LevelLoadError, BadMapError) as x:
        print("Unable to load level " + levelname + ".\n\nError:\n" + str(x))
    finally:
        pygame.quit()
MVC_triplets is just a dictionary (for non-python coders, it stores pairs made up of a key and a value, give it the key, and you get the value). The keys are the name's of the various active game_objects (e.g. "pacman", "ghost 1", "vulnerable ghost 2", "dot 13") and the value's are really basic representations of the objects. Each object is represented by a model, a view, and a controller. (Don't assume I'm actually following the MVC pattern; I think I am, but ... I don't know what I'm doing XD) There are several types of models, views, and controllers. For example, there's a ghost_like_model, a dot_like_model, and a fruit_model. Pacman uses a ghost_like_model combined with a pacman_view and a pacman_controller. The pieces of fruit use a fruit_model combined with a ghost_like_view and a fruit_controller. This is part of the file that holds all the models and stuff
class MVC_triplet:
    """Associate a model, view, and controller.

    Just a POD.

    """
    
    def __init__(self, model, view, controller):
        """Create an MVC_triplet."""
        self.model = model
        self.view = view
        self.controller = controller

class ghost_like_model:
    """Manage the model for anything that moves like a ghost.

    Pacman moves like this too!

    """
    
    def __init__(self, x, y, facing):
        """Create a ghost_like_model.

        x, y -- location.
        facing -- one of "left", "right", "up", or "down".

        """
        self.facing = facing
        self.rect = pygame.rect.Rect(x, y, 24, 24)

    def x(self):
        """Get x coordinate."""
        return self.rect.left

    def y(self):
        """Get y coordinate."""
        return self.rect.top

    def update(self):
        """Update the model."""
        assert(self.facing in ["left", "right", "up", "down"])
        if (self.facing == "left"):
            self.rect = self.rect.move(-1, 0)
        elif (self.facing == "right"):
            self.rect = self.rect.move(1, 0)
        elif (self.facing == "up"):
            self.rect = self.rect.move(0, -1)
        elif (self.facing == "down"):
            self.rect = self.rect.move(0, 1)

...

class dot_like_view:
    """Manage the on-screen appearence of a dot like game object."""

    def __init__(self, image, model):
        """Create a dot_like_view."""
        self.image = image
        self._model = model
        self.rect = model.rect
        self.last_rect = model.rect

    def update(self):
        """Update the view. This does nothing."""
        pass

...

class fruit_controller:
    """Control a piece of fruit."""

    def __init__(self, model):
        """Create a fruit_controller.

        model -- the model the fruit_controller will control. It should
        probably be a fruit_model.

        """
        self._model = model

    def update(self):
        """Update the model."""
        # Randomly change directions every once in a while (3/32 odds of
        # changing).
        if (1 == random.randint(1, 8)):
            directions = ["right", "left", "up", "down"]
            direction = random.randint(0, 3)
            self._model.facing = directions[direction]
This design worked out fine for me here, but Pacman is a pretty simple game. (question #1) Am I doing something wrong that is going to bite me later? (question #1.5) Also, is this actually the real MVC pattern? I've got the game's logic spread out in two places. Some of it is in the models and controllers, and the rest of it is in a function called game_logic(). game_logic() looks a little like this
def game_logic(MVC_triplets, dots_to_eat, walls, revive_zone,
               ghosts_vulnerable, vulnerable_time, dots_eaten,
               input_queue, level_width, level_height, fruits,
               fruit_start_x, fruit_start_y, fruit_alive_time,
               eaten_fruits, score):
    game_over = False
    added_objects = list()
    deleted_objects = list()
    # The cheat.
    for e in input_queue:
        if (e.input_type == "key"):
            if (e.details["key"] == "p"):
                ghosts_vulnerable = True
                vulnerable_time = 1000
                make_ghosts_vulnerable(MVC_triplets,
                                       added_objects,
                                       deleted_objects,
                                       walls)
                input_queue.remove(e)
    # Bounds checking.
    for triplet in MVC_triplets.values():
        if (triplet.model.x() < 0):
            triplet.model.rect.left = 288
        if (triplet.model.x() > level_width):
            triplet.model.rect.left = 0
        if (triplet.model.y() < 0):
            triplet.model.rect.top = level_width
        if (triplet.model.y() > level_height):
            triplet.model.rect.top = level_height

...

    # Maybe create fruit.
    if (len(eaten_fruits) < len(fruits)):
        r = random.randint(1, 200)
        if (r == 1):
            if (not "fruit" in MVC_triplets):
                # Have we already created and eaten a piece of fruit?
                if (len(eaten_fruits) == 0):
                    # No, make the first one.
                    MVC_triplets["fruit"] = create_fruit(fruits[0][0],
                                                         fruit_start_x,
                                                         fruit_start_y,
                                                         fruit_alive_time,
                                                         fruits[0][1])
                else:
                    # Yes, make the NEXT one.
                    next_fruit = None
                    for (i, s) in enumerate(fruits):
                        if (s[0] == eaten_fruits[-1].model.fruit_name):
                            next_fruit = fruits[i + 1]
                            break
                    MVC_triplets["fruit"] = create_fruit(next_fruit[0],
                                                         fruit_start_x,
                                                         fruit_start_y,
                                                         next_fruit[1],
                                                         fruit_alive_time)
    # Win!
    if (dots_eaten == dots_to_eat):
        print("You win!!!")
        game_over = True
    # Delete the destroyed objects and add new ones last.
    list(map(lambda e: MVC_triplets.__delitem__(e[0]),
             deleted_objects))
    list(map(lambda e: MVC_triplets.__setitem__(e[0], e[1]),
             added_objects))
    return (game_over, ghosts_vulnerable, vulnerable_time, dots_eaten,
            deleted_objects, eaten_fruits, score)
Again, this worked out fine for me here, but pacman is simple. (question #2) Should I be doing something different? This is becoming a really long post :(. Last question then. Right now, I'm basically shoving input into a list in my input handling function. The pacman controller and game_logic() have access to the list, and they take input out and do stuff. (question #3) What should I be doing? If that's really long / stupid / whatever, then I'm very sorry!

Share this post


Link to post
Share on other sites
Gongrats for finishing a game!

Your coding style is very self commenting which is nice. Also I'm happy to see you're using classes, constructors, private data members and such, which tells me you're not that beginner. I'm not a python expert but i'll drop my few cents in.

(question #1) Am I doing something wrong that is going to bite me later?
Sounds like you want to keep the code healthly for updates in future. You're mixing different types of models, views and controllers. If you want to add or change a feature in some game element, they all should have their own M V and C. Otherwise you need to hack around which feature is enabled or not per class.

(question #1.5) Also, is this actually the real MVC pattern?
MVC is defined how the M, V and C know about each others and what their responsibilities are. My undestanding is that:
- M is the model, data, for something.
- V contains the methods to view the data.
- C contains the logic to update the data.
There are multiple variations of the same basic idea. Check wiki. Also Model-View-Adapter, Model-View-ViewModel etc..

(question #2) Should I be doing something different?
Maybe isolate parts of the level_logic() to level_model, which explain the level layout. level_view which draws the level. And level_controller, which does the bounds checking for game entities.

(question #3) What should I be doing?
In MVC, only controller handles inputs. Maybe have pacman_controller class to handle that.

Rest of my cents... If the game is finished and bugfree, the code is perfect. Don't change a thing. But as this is a hobby project, if you still have motivation, start adding extra features to the game and it will show where the problems in desing are. If some new feature is tricky to add, think why is that. Also post here, I'd like to hear :)

Share this post


Link to post
Share on other sites
Thanks for the feedback.

I've playfully added a speed pellet that gives Pacman a permanent speed boost (I had to swap out my lame collision response for a proper minimum translation vector based one, because Pacman started glitching through the walls when you went straight at them XD), and a shield that allows Pacman to take a hit from a ghost without dying.

The reason I used some M, Vs, and Cs for multiple game objects was because if I had separated them, each of the separated versions would be the same as the others. All three types of ghost (normal, blue, and dead (the eyeballs)) and pacman did basically the same thing: they have a direction, and every time they update, they move in that direction at some speed. That's what the model did, it made them move in their direction (the controller changed the direction). (In order to add the shield for Pacman I had to give him his own model.)

When I wanted to make a change to the game that required that two objects which shared an M, V, or C to suddenly stop sharing, I just copied and pasted whatever needed to be split up, gave the copy a new name, and made whatever changes I needed to make to it. There is a file full of functions like create_pacman() and create_dead_ghost(), so I had to change some of those for each split.

Sharing MVCs never gave me any problems because it was a simple procedure to stop sharing them, and it seemed worthwhile because it cut down on the number of duplicate classes I had in mvcs.py. (numbering questions seemed to work decently before, so ... question #4) Would that procedure be more complicated in more complex game (e.g. a top down space shooter, like Galaga)?

I've been thinking about input. There's really only two kinds of input in the game right now, the arrow keys, the input from which is taken out of the input list by pacman's controller (currently an instance of the pacman_controller class), and the letter "p", which is processed by the game_logic() function and interpreted as a cheat code. game_logic() calls the helper function make_ghosts_vulnerable() (the same function it calls when pacman eats a power pellet, because entering the letter "p" makes all the ghost's turn blue for a short amount of time).

The reason the letter "p" is handled by game_logic() instead of the ghost's controllers (currently instances of the ghost_controller class) is because the ghosts are destroyed, and then replaced with vulnerable ghosts, a completely different game object. (The vulnerable ghost's use a different controller than the regular ghosts and the dead ghosts (they use the same model and view though), because all three types of ghost need different AI.) I've been putting any logic that involves a game object being created, destroyed, or replaced, or that involves an interaction between game objects in the game_logic() function.

I guess the ghost controller could have access to the input list, and when it received the letter "p" as input, it could send a message to game_logic() to replace the game object it controls (a ghost) with a new game object (a vulnerable ghost). (question #5) It seems like more trouble than it's worth for Pacman, but it might be a good idea for something more complex?

Another follow-up question. (question #6) Where do you put the logic for an interaction between two game objects? Should each object act separately, and the object that detects that the interaction should start would tell the other object that it needed to act too? (e.g. the Pacman object detects that it collided with a speed pellet object, the Pacman object increases its speed and reports the collision to the speed pellet, which then sends a message to game_logic() to delete it.) Or maybe an outside function would detect that the interaction should start and would tell both game objects about it? (e.g. the game_logic() function detects the collision and tells the Pacman object and the speed pellet object, which then do their thing.) (Maybe it would be someone else detecting the collision, like an object that represents the level?)

Thanks again :).

Share this post


Link to post
Share on other sites
I don't know if I can comment of the validity of the approach, but I took a similar but different approach with my PacMan clone - https://sourceforge.net/projects/aipac/

I defined MVC triads for sprites (ghosts and Pacman), dots, power pellets, and the maze itself. These were then collected into an enclosing MVC triad for the game world. I guess the end result is that I had a hierarchical MVC system with the world being the highest level and containing the other constituent MVCs.

This is essentially how I handled collision detection, etc. The World MVC handled the bulk of the game logic with each sub-MVC handling individual behaviors.

-Krik

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this