3
\$\begingroup\$

I'm rather new to programming. So far I've only done some small coding tasks once every now and then, and this the first "bigger" project for me. I love the idea of object-oriented programming and I've heard a lot of good things about Python and Pygame, so I decided to give it a shot and get coding.

The result is a house full of Cats and Mice (who share the parent class Animal). They move around randomly, stay in the enclosed area, and of course the Mice are afraid of Cats and flee in a panic if they get too close.

I tried my best to come up with a sensible class structure and to produce straightforward, easy-to read code with clear and concise comments.

I'm interested in every type of suggestions and tips, from a complete restructuring of the program to simple variable names and missing spaces. In case anyone wants the standalone *.exe, it can be downloaded here. Thanks for your time!

# The program simulates cats and mice moving inside an enclosed area called "house"
# Animals make turns at random intervals when they randomly choose a new direction and speed.
# When the have reached the selected direction and speed, they move straight ahead with constant speed
# until the next turn happens.
# When animals leave the boundary defined by the buffer zone, their next turn directs them back
# to the center of the house.
# Cats move around randomly but still respect the buffer zone.
# Mice also respect the buffer zone, but additionally flee from cats once they get too close
# The behaviour of cats and mice can be influenced through a variety of parameters to create a nice and realistic feel.

# <editor-fold desc="Preamble">

import pygame  # for awesomeness
import random  # for random numbers
import math  # for mathematical stuff
# import subprocess  # for creating the *.exe from command line

# House parameters
FRAME_RATE = 30
HOUSE_NAME = "House of Cat and Mouse™"
HOUSE_SIZE_HORIZONTAL = 800
HOUSE_SIZE_VERTICAL = 800
HOUSE_SIZE_BUFFER = 100
HOUSE_COLOR_FLOOR = (255, 215, 150)  # Light Brown [RGB]
HOUSE_COLOR_BORDER = (255, 140, 0)  # Orange [RGB]
HOUSE_LINE_WIDTH_BORDER = 5
MICE_NUMBER = 100
CATS_NUMBER = 2

# Mouse behavior
MOUSE_BEHAVIOR = dict(
    SPEED_MIN=0,
    SPEED_MAX=70,
    SPEED_CHANGE_MIN=0,
    SPEED_CHANGE_MAX=40,
    ACCELERATION_MIN=10,
    ACCELERATION_MAX=100,
    TURN_ANGLE_MIN=0.1 * math.pi,
    TURN_ANGLE_MAX=0.8 * math.pi,
    TURN_SPEED_MIN=1 * math.pi,
    TURN_SPEED_MAX=3 * math.pi,
    TURN_TIME_MIN=0.3,
    TURN_TIME_MAX=0.6,
    TURN_ANGLE_TOLERANCE_BUFFER=0.2 * math.pi,
    COLOR=(108, 110, 107),  # The Official Mouse Grey
    RADIUS=10  # Mice are slim
)
MOUSE_TURN_ANGLE_TOLERANCE_CAT = 0.1 * math.pi
MOUSE_DISTANCE_PANIC = 150
MOUSE_SPEED_PANIC = 200
MOUSE_TURN_SPEED_PANIC = 5 * math.pi
MOUSE_TURN_TIME_PANIC = 0.3

# Cat behavior
CAT_BEHAVIOR = dict(
    SPEED_MIN=30,
    SPEED_MAX=60,
    SPEED_CHANGE_MIN=0,
    SPEED_CHANGE_MAX=20,
    ACCELERATION_MIN=10,
    ACCELERATION_MAX=20,
    TURN_ANGLE_MIN=0 * math.pi,
    TURN_ANGLE_MAX=0.3 * math.pi,
    TURN_SPEED_MIN=0.5 * math.pi,
    TURN_SPEED_MAX=1 * math.pi,
    TURN_TIME_MIN=0.5,
    TURN_TIME_MAX=1,
    TURN_ANGLE_TOLERANCE_BUFFER=0.25 * math.pi,
    COLOR=(0, 0, 0),  # The Blackest Black
    RADIUS=20  # Cats are fat
)


# </editor-fold>

# Top class, contains the animals and directs the flow of the program
class House:
    # Animals stored as class variables to give cat and mouse instances access to them
    # Needed to let the mice check if cats are near
    mice = None  # Object to store all the mice
    cats = None  # Object to store all the cats

    def __init__(self):
        self.frame_rate = FRAME_RATE  # Maximum frame rate (can be lower) [1/s]
        self.name = HOUSE_NAME  # Name of the house, shown on the window bar [string]
        self.size_horizontal = HOUSE_SIZE_HORIZONTAL  # Width of the house [px]
        self.size_vertical = HOUSE_SIZE_VERTICAL  # Height of the house [px]
        self.size_buffer = HOUSE_SIZE_BUFFER  # Width of buffer at house edges that makes animals turn back [px]
        self.color_floor = HOUSE_COLOR_FLOOR  # Color of the background [RGB]
        self.color_border = HOUSE_COLOR_BORDER  # Color of the border [RGB]
        self.line_width_border = HOUSE_LINE_WIDTH_BORDER  # Line width of the border to the buffer zone [px]
        self.mice_number = MICE_NUMBER  # Number of mice in the house [-]
        self.cats_number = CATS_NUMBER  # Number of cats in the house [-]

        House.mice = []  # Object to store all the mice
        House.cats = []  # Object to store all the cats
        self.program_window = pygame.display.set_mode((self.size_horizontal, self.size_vertical))  # Create window
        pygame.display.set_caption(self.name)  # Set name of window
        self.clock = pygame.time.Clock()  # Set game clock (takes care of frame rate)

        self.create_mice()  # Create all the mice
        self.create_cats()  # Create all the cats

    # Create self.mice_number mice in the house
    def create_mice(self):
        for i in range(self.mice_number):
            House.mice.append(Mouse())

    # Create self.cats_number cats in the house
    def create_cats(self):  # Create self.cats_number cats in the house
        for i in range(self.cats_number):
            House.cats.append(Cat())

    # Updates movement of all animals in the house
    def update_animal_movement(self):
        for i in House.mice:
            i.update_position()  # Update coordinates (happens every frame)
            if i.frame_number_current >= i.frame_number_end_of_turn:  # Do turn when the current turn_time is reached
                i.do_turn()
        for i in House.cats:
            i.update_position()  # Update coordinates (happens every frame)
            if i.frame_number_current >= i.frame_number_end_of_turn:  # Do turn when the current turn_time is reached
                i.do_turn()
        self.clock.tick(FRAME_RATE)  # Wait till next frame

    # Draws the house and all the animals contained
    def draw(self):
        self.program_window.fill(self.color_floor)  # Fill window with floor color (covers previous frame)
        pygame.draw.rect(self.program_window, self.color_border,  # Draw border to buffer zone
                         (self.size_buffer, self.size_buffer, self.size_horizontal - 2 * self.size_buffer,
                          self.size_vertical - 2 * self.size_buffer), self.line_width_border)
        for i in House.mice:  # Draw all the mice
            i.draw()
        for i in House.cats:  # Draw all the cats
            i.draw()
        pygame.display.flip()  # Update whole window area


# Parent class of cats and mice, defines most of their general behaviour
class Animal:
    def __init__(self, animal_behaviour):
        self.speed_min = animal_behaviour["SPEED_MIN"]  # Minimum move speed of the animal [px/s]
        self.speed_max = animal_behaviour["SPEED_MAX"]  # Maximum move speed of the animal [px/s]
        # Minimum change of speed from the start to the end of a turn [px/s]
        self.speed_change_min = animal_behaviour["SPEED_CHANGE_MIN"]
        # Maximum change of speed from the start to the end of a turn [px/s]
        self.speed_change_max = animal_behaviour["SPEED_CHANGE_MAX"]
        # Minimum acceleration when changing speed [px/s^2]
        self.acceleration_min = animal_behaviour["ACCELERATION_MIN"]
        # Maximum acceleration when changing speed [px/s^2]
        self.acceleration_max = animal_behaviour["ACCELERATION_MAX"]
        self.turn_angle_min = animal_behaviour["TURN_ANGLE_MIN"]  # Minimum change of direction when turning [rad]
        self.turn_angle_max = animal_behaviour["TURN_ANGLE_MAX"]  # Maximum change of direction when turning [rad]
        self.turn_speed_min = animal_behaviour["TURN_SPEED_MIN"]  # Minimum angular velocity of direction change [rad/s]
        self.turn_speed_max = animal_behaviour["TURN_SPEED_MAX"]  # Maximum angular velocity of direction change [rad/s]
        self.turn_time_min = animal_behaviour["TURN_TIME_MIN"]  # Minimum time to next turn of the animal [s]
        self.turn_time_max = animal_behaviour["TURN_TIME_MAX"]  # Maximum time to next turn of the animal [s]
        # Acceptable direction difference to the center of the window when returning from the buffer zone [rad]
        self.turn_angle_tolerance_buffer = animal_behaviour["TURN_ANGLE_TOLERANCE_BUFFER"]
        self.color = animal_behaviour["COLOR"]  # Color of the animal [RGB]
        self.radius = animal_behaviour["RADIUS"]  # Radius of the circle that represents the animal [px]

        self.speed_current = None  # Current speed of the animal [px/s]
        self.speed_end_of_turn = None  # Target speed at the end of the current turn [px/s]
        self.acceleration = None  # Acceleration while changing speed for the current turn [px/s^2]
        # Speed change per frame while changing speed, equals self.acceleration / FRAME_RATE [px/s]
        self.speed_change_per_frame = None
        self.direction_current = None  # Current movement direction of the animal (0 means left, pi/2 means down) [rad]
        # Target movement direction at the end of the current turn (0 means left, pi/2 means down) [px/s]
        self.direction_end_of_turn = None
        self.turn_speed = None  # Angular velocity while changing direction for the current turn [rad/s]
        self.turn_time = None  # Duration of the current turn [s]
        # Direction change per frame while changing direction, equals self.turn_speed / FRAME_RATE [rad]
        self.direction_change_per_frame = None
        # Current horizontal coordinate of animal (distance from left edge of the window) [px]
        self.position_horizontal = None
        # Current vertical coordinate of animal (distance from top edge of the window) [px]
        self.position_vertical = None
        self.frame_number_current = None  # Number of frames since the start of the current turn [-]
        self.frame_number_end_of_turn = None  # Number of frames when the current turn will end [-]
        self.is_accelerating = None  # Check if animal is increasing speed in the current turn [bool]
        self.is_turning_clockwise = None  # Check if animal is turning clockwise in the current turn [bool]

        self.set_initial_state()  # Set start conditions (speed, direction) of the animal
        self.do_turn()  # Defines first turn of the animal

    # Set start conditions (speed, direction) of the animal
    def set_initial_state(self):
        self.speed_current = random.uniform(self.speed_min, self.speed_max)
        self.direction_current = random.uniform(-math.pi, math.pi)
        self.position_horizontal = random.uniform(HOUSE_SIZE_BUFFER, HOUSE_SIZE_HORIZONTAL - HOUSE_SIZE_BUFFER)
        self.position_vertical = random.uniform(HOUSE_SIZE_BUFFER, HOUSE_SIZE_VERTICAL - HOUSE_SIZE_BUFFER)

    # Placeholder for the execution of a turn (cats and mice move differently)
    def do_turn(self):
        pass

    # Executes a turn when the animal is relaxed (not in buffer, not near cat for mice only)
    def do_turn_relaxed(self):
        # Randomly increase/decrease speed
        self.speed_end_of_turn = self.speed_current + random.choice([1, -1]) * random.uniform(self.speed_change_min,
                                                                                              self.speed_change_max)
        if self.speed_end_of_turn > self.speed_max:  # Set speed to maximum if value turned out bigger
            self.speed_end_of_turn = self.speed_max
        if self.speed_end_of_turn < self.speed_min:  # Set speed to minimum if value turned out smaller
            self.speed_end_of_turn = self.speed_min
        # Randomly change direction
        self.direction_end_of_turn = self.direction_current + random.choice([1, -1]) * random.uniform(
            self.turn_angle_min, self.turn_angle_max)
        self.acceleration = random.uniform(self.acceleration_min, self.acceleration_max)  # Select random acceleration
        self.turn_speed = random.uniform(self.turn_speed_min, self.turn_speed_max)  # Select random turn speed
        self.turn_time = random.uniform(self.turn_time_min, self.turn_time_max)  # Select random turn time

    # Executes a turn when the animal is in the buffer zone (close to the edges)
    def do_turn_in_buffer(self):
        self.speed_end_of_turn = self.speed_max  # Move with maximum speed to get back inside quickly
        # Move approximately towards the center of the house
        self.direction_end_of_turn = self.direction_to_point(HOUSE_SIZE_HORIZONTAL / 2,
                                                             HOUSE_SIZE_VERTICAL / 2) + random.uniform(
            -self.turn_angle_tolerance_buffer, self.turn_angle_tolerance_buffer)
        self.acceleration = self.acceleration_max  # Accelerate as quick as possible
        self.turn_speed = self.turn_speed_max  # Turn as quick as possible
        self.turn_time = self.turn_time_max  # Go towards center for the longest time possible (to gain some separation)

    # Determines whether the animal is in the buffer zone (true if close to the edges) [bool]
    def in_buffer(self):
        if (self.position_horizontal < HOUSE_SIZE_BUFFER or
                self.position_horizontal > HOUSE_SIZE_HORIZONTAL - HOUSE_SIZE_BUFFER or
                self.position_vertical < HOUSE_SIZE_BUFFER or
                self.position_vertical > HOUSE_SIZE_VERTICAL - HOUSE_SIZE_BUFFER):
            return True
        else:
            return False

    # Determines angular direction from animal to the given point [rad]
    def direction_to_point(self, x, y):
        return math.atan2(-self.position_vertical + y, -self.position_horizontal + x)

    # Contains the operations ALL ANIMALS need after executing a turn (setting variables etc)
    def edit_parameters_after_doing_turn(self):
        # Keeps the direction value between -pi and pi
        self.direction_current = math.remainder(self.direction_current, 2 * math.pi)
        self.direction_end_of_turn = math.remainder(self.direction_end_of_turn, 2 * math.pi)
        # Checks if animal is accelerating (increasing speed) in current turn
        if self.speed_current < self.speed_end_of_turn:
            self.is_accelerating = True
        else:
            self.is_accelerating = False
        # Checks if clockwise or counterclockwise rotation is quicker (true if clockwise is quicker)
        if math.remainder(self.direction_current - self.direction_end_of_turn, 2 * math.pi) < 0:
            self.is_turning_clockwise = True
        else:
            self.is_turning_clockwise = False
        self.speed_change_per_frame = self.acceleration / FRAME_RATE
        self.direction_change_per_frame = self.turn_speed / FRAME_RATE
        self.frame_number_end_of_turn = self.turn_time * FRAME_RATE
        self.frame_number_current = 0  # Resets frame counter (determines end of turn)

    # Updates the movement and coordinates of the animal based on target values for current turn (happens every frame)
    def update_position(self):
        # If accelerating, increase speed until target speed reached
        if self.is_accelerating and self.speed_current < self.speed_end_of_turn:
            self.speed_current += self.speed_change_per_frame
        # If slowing down, decrease speed until target speed reached
        elif not self.is_accelerating and self.speed_current > self.speed_end_of_turn:
            self.speed_current -= self.speed_change_per_frame
        # If turning clockwise, turn until target direction is reached
        # (complicated because direction values can switch from positive to negative)
        if self.is_turning_clockwise:
            if self.direction_end_of_turn > 0 and self.direction_current < self.direction_end_of_turn:
                self.direction_current += self.direction_change_per_frame
            elif self.direction_end_of_turn < 0 and self.direction_current < self.direction_end_of_turn + math.pi * (
                    abs(self.direction_current) + self.direction_current) / abs(self.direction_current):
                self.direction_current += self.direction_change_per_frame
        # If turning counterclockwise, turn until target direction is reached
        # (complicated because direction values can switch from negative to positive)
        else:
            if self.direction_end_of_turn < 0 and self.direction_current > self.direction_end_of_turn:
                self.direction_current -= self.direction_change_per_frame
            elif self.direction_end_of_turn > 0 and self.direction_current > self.direction_end_of_turn - math.pi * (
                    abs(self.direction_current) - self.direction_current) / abs(self.direction_current):
                self.direction_current -= self.direction_change_per_frame
        # Update horizontal position
        self.position_horizontal += math.cos(self.direction_current) * self.speed_current / FRAME_RATE
        # Update horizontal position
        self.position_vertical += math.sin(self.direction_current) * self.speed_current / FRAME_RATE
        self.frame_number_current += 1  # Increase frame counter (determines end of turn)

    # Draws the animal
    def draw(self):
        pygame.draw.circle(house.program_window, self.color, (round(self.position_horizontal),
                                                              round(self.position_vertical)), self.radius, 0)


# The Cat class is almost identical to the parent class Animal
# Cats, just like raptors, don't know fear so they don't need any special behavior
class Cat(Animal):
    def __init__(self):
        Animal.__init__(self, CAT_BEHAVIOR)  # Cat-specific movement

    # Executes cat turn
    def do_turn(self):
        if self.in_buffer():  # Move towards center when in buffer zone (close to edges)
            self.do_turn_in_buffer()
        else:  # Else stay relaxed and move random
            self.do_turn_relaxed()
        self.edit_parameters_after_doing_turn()  # General stuff after turn of all Animals


# Mouses are cowards and run away from cats, so they need more special features
class Mouse(Animal):
    def __init__(self):
        Animal.__init__(self, MOUSE_BEHAVIOR)  # Mouse-specific movement (quicker than cats)
        # Angle tolerance when mouse flees from cat [rad]
        self.turn_angle_tolerance_cat = MOUSE_TURN_ANGLE_TOLERANCE_CAT
        self.distance_panic = MOUSE_DISTANCE_PANIC  # Distance to a cat that makes mice panic and flee [px]
        self.speed_panic = MOUSE_SPEED_PANIC  # Mice run extra fast when they panic [px/s]
        self.turn_speed_panic = MOUSE_TURN_SPEED_PANIC  # Mice turn extra fast when they panic [rad/s]
        # Mice panic for short durations at a time, which sometimes makes them run zig-zag (looks nice) [s]
        self.turn_time_panic = MOUSE_TURN_TIME_PANIC

        self.is_near_cat = None  # Is me near cat? (true if cat near) [bool]

    # Checks if cat is near. Returns ID of cat if cat is near, otherwise returns false [ID or bool]
    def near_cat(self):
        for i in House.cats:
            if math.sqrt((self.position_horizontal - i.position_horizontal) ** 2 +
                         (self.position_vertical - i.position_vertical) ** 2) < self.distance_panic:
                return i
        return False

    # Executes mouse turn
    def do_turn(self):
        self.is_near_cat = self.near_cat()
        if self.is_near_cat is not False:  # Fleeing from cats is first priority
            self.do_turn_near_cat()
        elif self.in_buffer():  # Only think about buffer zone when far away from cats
            self.do_turn_in_buffer()
        else:
            self.do_turn_relaxed()  # Else, stay relaxed and move slowly and randomly
        self.edit_parameters_after_doing_turn()  # General stuff after turn of all Animals

    # Executes a turn when the mouse is near a cat
    def do_turn_near_cat(self):
        self.speed_end_of_turn = self.speed_panic  # Set speed to panic mode
        # Set direction away from cat (with some tolerance)
        self.direction_end_of_turn = math.remainder(self.direction_to_point(self.is_near_cat.position_horizontal,
                                     self.is_near_cat.position_vertical) + math.pi, 2 * math.pi) + random.uniform(
                                    -self.turn_angle_tolerance_cat, self.turn_angle_tolerance_cat)
        self.acceleration = self.acceleration_max  # Set acceleration to maximum
        self.turn_speed = self.turn_speed_panic  # Set turn speed to panic mode
        # Set turn time to panic mode (shorter, makes for nice zig-zag runs sometimes)
        self.turn_time = self.turn_time_panic


# Main loop, where it all comes together
if __name__ == "__main__":
    # Creates standalone executable
    # subprocess.call(
    #    'pyinstaller HouseOfCatAndMouse.py --onefile --name HouseOfCatAndMouse --log-level ERROR -w', shell=True)
    pygame.init()  # Initialize pygame (whatever this does)
    house = House()  # Build a house and fill it with animals
    running = True
    while running:
        for event in pygame.event.get():  # Check if user terminates program (needed to prevent crash every time)
            if event.type == pygame.QUIT:
                running = False
        house.update_animal_movement()  # Update animal movement in the house
        house.draw()  # Draw house and content
\$\endgroup\$
3
\$\begingroup\$

So many good things, so many weird things.

First of all, I love your comments. They made this review very easy.

With that said, I have a couple major complaints:

dict is not very type-safe; it requires you to access common properties by strings. I highly suggest storing those in a class instead, maybe something like AnimalProperties? That would be the smallest step from where you're at now.

My suggestion would be to either take full advantage of named parameters (shoutout to **kwargs), or configure the Cat and Mouse properties externally. Having them in the same script as a dict doesn't really sit too well with me.

And I'd say take a look at whether all of your boolean fields are necessary. For example, in Mouse, self.is_near_cat is only used to hold the result from self.near_cat(), which either seems to either return False (should be None) if not near a Cat or a Cat if it's near one. You can pass that cat value to self.do_turn_near_cat as self.do_turn_near_cat(self, cat), because that method should require a Cat to begin with, and thus remove the need for that boolean field in Mouse.

Also, avoid writing confusing things like if self.is_near_cat is not False, because I immediately thought "sooo, if it's True?" I said to use None above because None is falsy.

To drive that particular example home:

    # Checks if cat is near. Returns ID of cat if cat is near, otherwise returns false [ID or bool]
    def near_cat(self):
        for i in House.cats:
            if math.sqrt((self.position_horizontal - i.position_horizontal) ** 2 +
                         (self.position_vertical - i.position_vertical) ** 2) < self.distance_panic:
                return i
        return None

    # Executes mouse turn
    def do_turn(self):
        near_cat = self.near_cat()
        if near_cat:  # Fleeing from cats is first priority
            self.do_turn_near_cat(near_cat)
        elif self.in_buffer():  # Only think about buffer zone when far away from cats
            self.do_turn_in_buffer()
        else:
            self.do_turn_relaxed()  # Else, stay relaxed and move slowly and randomly
        self.edit_parameters_after_doing_turn()  # General stuff after turn of all Animals

    # Executes a turn when the mouse is near a cat
    def do_turn_near_cat(self, cat):
        self.speed_end_of_turn = self.speed_panic  # Set speed to panic mode
        # Set direction away from cat (with some tolerance)
        self.direction_end_of_turn = math.remainder(self.direction_to_point(cat.position_horizontal,
                                     cat.position_vertical) + math.pi, 2 * math.pi) + random.uniform(
                                    -self.turn_angle_tolerance_cat, self.turn_angle_tolerance_cat)
        self.acceleration = self.acceleration_max  # Set acceleration to maximum
        self.turn_speed = self.turn_speed_panic  # Set turn speed to panic mode
        # Set turn time to panic mode (shorter, makes for nice zig-zag runs sometimes)
        self.turn_time = self.turn_time_panic

There are many details like this, so try to look at all your functions, what they require, and model those appropriately, and take a closer look at the necessity of all your class fields.

\$\endgroup\$
  • \$\begingroup\$ Thanks! About the dict-part, would it make sense to store the values inside an own class from the OOP-point-of-view? (It's not like they are a seperate "physical entity" from the actual animal). Also, as you mention the **kwargs, would it be better practice to use **kwargs or a class here? Also thanks for the near_cat example, would've never thought of doing it like this! \$\endgroup\$ – MaxD Jul 4 at 18:29
  • 1
    \$\begingroup\$ I agree with @AleksandrH. The repetition of those named values indicates that they are better served as properties of the Animal class. **kwargs is mostly just to add extra named properties, but since the extra named properties for Mouse seem mandatory, I would advise not using it here. Instead, I would suggest accepting them in the init method as named parameters with your configured values as defaults. \$\endgroup\$ – TCFP Jul 4 at 18:46
1
\$\begingroup\$

I'll comment on two specific things I noticed while briefly reviewing (I hope I don't come across as harsh where I'm just stating observations):

  • OOP: One thing that stood out to me immediately is the dictionaries. The beauty of OOP is that it allows object hierarchies, and for those objects to inherit properties from their base class. A good rule of thumb: If you ever find yourself copy-pasting code, you're doing something wrong. Both CAT_BEHAVIOR and MOUSE_BEHAVIOR have the same entries in the dictionary with just different value assignments—why not make those keys properties of the Animal base class?

  • Comments and functions: For the math calculations, your comments were helpful (even though I still didn't understand the math; I didn't have time to carefully review it, unfortunately). That said, there are a lot of "useless" comments, or ones that don't belong in good code. These are "what" comments that basically restate what the code is doing instead of providing insight. For example, mice = None # Object to store all the mice, House.mice = [] # Object to store all the mice, and self.create_mice() # Create all the mice. And of course their cat variants. As well as these two: # Create self.mice_number mice in the house and # Draw all the mice. There are also comments on things that are in fact unclear because of poor naming (either on your part or pygame's). For example: pygame.display.flip() # Update whole window area. I would wrap this in a function named exactly that: update_window_area(self). But this by far is the worst offender, in my opinion: edit_parameters_after_doing_turn() # General stuff after turn of all Animals. Not only is the function itself quite long and doing multiple complex computations, but the name itself is also not too informative (and the comment doesn't help!). Consider giving each animal an Update method—Update is pretty idiomatic in game dev as a loop that should run on each frame update/tick. Within Update, use multiple low-level methods that do one thing each.

\$\endgroup\$
  • \$\begingroup\$ Thanks! About the first part, how could I make the values part of the animal-class if they are different for cat and mice? Would I just set them to "None" in the animal-class and define them in the cat/mouse-class? \$\endgroup\$ – MaxD Jul 4 at 18:12
  • 1
    \$\begingroup\$ You'd define properties using self.propertyName = value and then set the values within the concrete derived classes. Search "python inheritance" online for some examples if you're curious :) \$\endgroup\$ – AleksandrH Jul 4 at 18:22
  • \$\begingroup\$ So I would give every animal some "base stats" that are later overridden in the child classes, is this correct? \$\endgroup\$ – MaxD Jul 4 at 18:25
  • 1
    \$\begingroup\$ @MaxD If by base stats you mean properties that belong to all Animals (all instances of the Animal class), then yep. All derived classes (classes that extend Animal) will inherit those properties. Meaning in the mouse class, you could do self.nameOfPropertInBaseClass to set the value. Of course, the derived classes need not only rely on their base class properties; they can also define some of their own. \$\endgroup\$ – AleksandrH Jul 5 at 0:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.