Observer Pattern Example

from abc import ABC, abstractmethod
import pygame
import random

# Observer interface
class Observer(ABC):
    abstractmethod
    def update(self, subject):
        pass

# Concrete observer class
class Rectangle(Observer):
    def __init__(self, x, y, width, height, color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.color = color

    def draw(self, screen):
        pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height))

    def update(self, subject):
        self.color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

# Subject class
class Circle:
    def __init__(self, x, y, radius, color):
        self.x = x
        self.y = y
        self.radius = radius
        self.color = color
        self.observers = []

    def draw(self, screen):
        pygame.draw.circle(screen, self.color, (self.x, self.y), self.radius)

    def attach(self, observer):
        self.observers.append(observer)

    def notify(self):
        for observer in self.observers:
            observer.update(self)

    def move(self, x, y):
        self.x = x
        self.y = y
        self.notify()

def main():
    pygame.init()
    screen = pygame.display.set_mode((800, 600))
    pygame.display.set_caption("Observer Design Pattern with Pygame")

    running = True
    clock = pygame.time.Clock()

    # Instance of our Publisher
    circle = Circle(400, 300, 50, (255, 255, 255))

    # Three instances of our subscriber/observer classes.
    rectangles = [
        Rectangle(100, 100, 50, 50, (255, 0, 0)),
        Rectangle(200, 200, 50, 50, (0, 255, 0)),
        Rectangle(300, 300, 50, 50, (0, 0, 255)),
    ]

    # Add the three rectangles as our Subsribers to the Circle class 
    for rect in rectangles:
        circle.attach(rect)

    # Start the game loop
    while running:
        # refrsh and clear the screen with black background
        screen.fill((0, 0, 0))

        # listen for any in-game events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # draw the circle at its current position
        circle.draw(screen)

        # draw each of the rectangles
        for rect in rectangles:
            rect.draw(screen)

        # get the current mouse position/location
        mouse_pos = pygame.mouse.get_pos()

        # check if the mouse button is pressed
        if pygame.mouse.get_pressed()[0]:
            circle.move(*mouse_pos) # drag the circle 

        # display the screen buffer (i.e. screen contents)
        pygame.display.flip()
        # generate 60 frames per second
        clock.tick(60)

    pygame.quit()

if __name__ == "__main__":
    main()