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)
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()