pythonpygamewindowobserver-patternmulti-agent

Can't interact the main window pygame


I tried to implement a Multiagent System using pygame. I created a class for the System "SMA", a class for the environment "EnvSMA" and a class Agent with different behaviour. When an agent collides with a "wall" one of the limit of the grid, it behaves like the ball on pong game, when it collides with another agent, it exchanges its direction with the other one. I've implemented the observer design pattern also.

import sys
import pygame
from typing import List
from random import randint as rd
from abc import ABC, abstractmethod
from matplotlib import pyplot as plt

collAgAg = []
collAgW = []
t = []

class Subject(ABC):

    @abstractmethod
    def attach(self, observer) -> None:
        pass

    @abstractmethod
    def detach(self, observer) -> None:
        pass

    @abstractmethod
    def notify(self) -> None:
        pass


class Observer(ABC):

    @abstractmethod
    def update(self, subject) -> None:
        pass


class SMA(Subject):

    _observers: List[Observer] = []

    def __init__(self, width, height, nbCaseX, nbCaseY, nbAgent, nbTour, isToric):
        self.env = EnvSMA(); self.nbTour = nbTour
        self.env.init(width,height,nbCaseX,nbCaseY,nbAgent,isToric)

    def run(self):
        i = 0
        while (i != self.nbTour):
            for agent in self.env.agents:
                agent.decide()
                self.notify()
            i += 1

    def attach(self, observer : Observer) -> None:
        self._observers.append(observer)

    def detach(self, observer : Observer) -> None:
        self._observers.remove(observer)

    def notify(self) -> None:
        for observer in self._observers:
            observer.update(self)


class EnvSMA:
    
    def __init__(self):
        pass
        
    def init(self,width,height,nbCaseX,nbCaseY,nbAgent,isToric):
        self.nbCaseX = nbCaseX; self.nbCaseY = nbCaseY;
        self.width = width; self.height = height;
        self.isToric = isToric; self.agents = [];

        # X colonne Y ligne
        self.spaceAg = [[0 for i in range(nbCaseX)] for j in range(nbCaseY)]
        
        for i in range(1,nbAgent+1):
            ag = Agent(self); ag.id = i;
            
            # Nombres random pour une case quelconque de la grille
            x1 = rd(0,self.nbCaseX-1);
            y1 = rd(0,self.nbCaseY-1);

            # Conversion [0,WIDTH-1] vers [0,nbCase-1]
            x = int(x1*(self.width/self.nbCaseX))
            y = int(y1*(self.height/self.nbCaseY))

            self.placerAgent(ag,x,y); 
            ag.set_pasXY((self.width/self.nbCaseX)*rd(-1,1),
                         (self.width/self.nbCaseX)*rd(-1,1));
            ag.color = (rd(0,255),rd(0,255),rd(0,255))
            self.agents.append(ag);
            
    def placerAgent(self,ag,x,y):
        # conversion [0,WIDTH-1] vers [0,nbCase-1]
        x1 = int(x*self.nbCaseX/self.width)
        y1 = int(y*self.nbCaseY/self.height)
        if (not(self.spaceAg[y1][x1])):
            ag.set_coord(x,y);
            self.spaceAg[y1][x1] = ag.id;
        else:
            # Nombres random pour une case quelconque de la grille
            x1 = rd(0, self.nbCaseX - 1);
            y1 = rd(0, self.nbCaseY - 1);

            # Conversion [0,WIDTH-1] vers [0,nbCase-1]
            x = int(x1 * (self.width / self.nbCaseX))
            y = int(y1 * (self.height / self.nbCaseY))
            self.placerAgent(ag, x, y);


class Agent:
    
    # Agent init
    def __init__(self,env):
        self.env = env; self.id = 0;
        self.posX = 0; self.posY = 0;
        self.pasX = 1; self.pasY = 1;
        self.color = (0,0,0); 
        
        
    def set_coord(self,x,y):
        self.posX = x; self.posY = y;

    def set_pasXY(self,x,y):
        self.pasX = x
        self.pasY = y

    def decide(self):
        if(not(self.env.isToric)):
            # Real Current Coordinate
            crtCvCoord = (int(self.posX*self.env.nbCaseX/self.env.width),
                          int(self.posY*self.env.nbCaseY/self.env.height));
            
            if(self.posY+self.pasY <= 0 or self.posY+self.pasY >= self.env.height):
                self.pasY *= -1
            if(self.posX+self.pasX <= 0 or self.posX+self.pasX >= self.env.width):
                self.pasX *= -1

            # Real Next Coordinate 
            nxCrd = (self.posX+self.pasX,self.posY+self.pasY)
            # Converted Next Coordinate 
            nxCvCrd = (int(nxCrd[0]*self.env.nbCaseX/self.env.width),
                       int(nxCrd[1]*self.env.nbCaseY/self.env.height));
            
            if(not(self.env.spaceAg[nxCvCrd[1]][nxCvCrd[0]])):
                # go ahead
                self.env.spaceAg[crtCvCoord[1]][crtCvCoord[0]] = 0
                self.env.spaceAg[nxCvCrd[1]][nxCvCrd[0]] = self.id
                self.set_coord(nxCrd[0],nxCrd[1])
            else:
                # Collision with another agent
                idOtherAgent = self.env.spaceAg[nxCvCrd[1]][nxCvCrd[0]]
                otherAgent = self.env.agents[idOtherAgent-1]
                self.pasX,otherAgent.pasX = otherAgent.pasX,self.pasX
                self.pasY,otherAgent.pasY = otherAgent.pasY,self.pasY
        else:
            # Real Current Coordinate
            crtCvCoord = (int(self.posX * self.env.nbCaseX / self.env.width),
                          int(self.posY * self.env.nbCaseY / self.env.height));

            if (self.posY + self.pasY <= 0):
                self.pasY *= -1
            if (self.posY + self.pasY >= self.env.height):
                self.pasY
            if (self.posX + self.pasX <= 0):
                self.pasX *= -1
            if (self.posX + self.pasX >= self.env.width):
                self.pasX

            # Real Next Coordinate
            nxCrd = (self.posX + self.pasX, self.posY + self.pasY)
            # Converted Next Coordinate
            nxCvCrd = (int(nxCrd[0] * self.env.nbCaseX / self.env.width),
                       int(nxCrd[1] * self.env.nbCaseY / self.env.height));

            if (not (self.env.spaceAg[nxCvCrd[1]][nxCvCrd[0]])):
                # go ahead
                self.env.spaceAg[crtCvCoord[1]][crtCvCoord[0]] = 0
                self.env.spaceAg[nxCvCrd[1]][nxCvCrd[0]] = self.id
                self.set_coord(nxCrd[0], nxCrd[1])
            else:
                # Collision with another agent
                idOtherAgent = self.env.spaceAg[nxCvCrd[1]][nxCvCrd[0]]
                otherAgent = self.env.agents[idOtherAgent - 1]
                self.pasX, otherAgent.pasX = otherAgent.pasX, self.pasX
                self.pasY, otherAgent.pasY = otherAgent.pasY, self.pasY

WHITE = (255,255,255)

class Vue:
    def __init__(self,surface):
        self.surface = surface
        self.timeApp = 0

    def update(self,subject) -> None:
        clock = pygame.time.Clock()
        dt = clock.tick(60)
        self.timeApp += (dt/100)
        t.append(self.timeApp)
        self.stepX = int(subject.env.width / subject.env.nbCaseX)
        self.stepY = int(subject.env.height / subject.env.nbCaseY)
        self.surface.fill((0,0,0))

        for x in range(0,subject.env.nbCaseX):
            pygame.draw.line(self.surface,WHITE,
                             (x*self.stepX,0),
                             (x*self.stepX,subject.env.height));
        for y in range(subject.env.height):
            pygame.draw.line(self.surface,WHITE,
                             (0,y*self.stepY),
                             (subject.env.width,y*self.stepY));
        for agent in subject.env.agents:
            agentRect = pygame.Rect((int(agent.posX), int(agent.posY)), (self.stepX, self.stepY))
            pygame.draw.rect(self.surface,agent.color,agentRect)
        pygame.display.flip()



pygame.init()

WIDTH = 400
HEIGHT = 400

pygame.display.set_caption("SMA Abidine")
screen = pygame.display.set_mode((WIDTH, HEIGHT))

def main(argv):

    sma = SMA(WIDTH,HEIGHT,100,100,1000,10,False)
    vue = Vue(screen)
    sma.attach(vue)
    sma.run()
    pygame.quit()

if __name__ == "__main__":
    main(sys.argv)

Solution

  • You have to handle for events, which you can get using pygame.event.get() . The code works for me when I added it to the update function. Not sure if the function was intended to be used this way, but you get the idea. Program doesn't crash anymore:

    def update(self,subject) -> None:
            while True:
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        pygame.quit()
                clock = pygame.time.Clock()
                dt = clock.tick(60)
                self.timeApp += (dt/100)
                t.append(self.timeApp)
                self.stepX = int(subject.env.width / subject.env.nbCaseX)
                self.stepY = int(subject.env.height / subject.env.nbCaseY)
                self.surface.fill((0,0,0))
    
                for x in range(0,subject.env.nbCaseX):
                    pygame.draw.line(self.surface,WHITE,
                                     (x*self.stepX,0),
                                     (x*self.stepX,subject.env.height));
                for y in range(subject.env.height):
                    pygame.draw.line(self.surface,WHITE,
                                     (0,y*self.stepY),
                                     (subject.env.width,y*self.stepY));
                for agent in subject.env.agents:
                    agentRect = pygame.Rect((int(agent.posX), int(agent.posY)), (self.stepX, self.stepY))
                    pygame.draw.rect(self.surface,agent.color,agentRect)
                pygame.display.flip()