Python procédural en POO

Fermé
elmnser1 - Modifié le 14 avril 2023 à 14:39
 desir_indecis - 14 avril 2023 à 21:22

Bonjour, 

J'ai un code python écrit en procédural je veux le convertir en orienté objet. Est-ce que quelqu'un peut m'aider ?

import sys, pygame

pygame.init()
BLACK=0,0,0
BLUE=0,0,255
GREEN=0,255,0
WHITE=255,255,255
size = width, height = 660, 460
FRAMES_PER_SECOND = 10 
clock = pygame.time.Clock() 
screen = pygame.display.set_mode(size)
def draw_Barre_verticale(left,h):
    pygame.draw.rect(screen, BLUE, pygame.Rect(left, 0, 10, h))

def draw_Barre_horizontale(top,w):
    pygame.draw.rect(screen, BLUE, pygame.Rect(0, top, w, 10))
def draw_chariot(X,Y, Ref):

    pygame.draw.rect(screen, GREEN, pygame.Rect(X-20+5, Y-20+5, 40, 40))
    font = pygame.font.SysFont("arial", 10)
    texte_x = pygame.font.Font.render(font, Ref, True, (0,0,0))
    screen.blit(texte_x, dest=(X, Y))
def trouver_case_vide():
    for j in range(5):
        for i in range(5):
            if LL[j][i]=='':
                return i,j
    #si aucune vide
    return None,None

def retour_pos_init():
    global X, Y, deltaX, deltaY
    if X>X0:
        X=X-deltaX
        if X<X0: X=X0
    if Y<Y0:
        Y=Y-deltaY
        if Y>Y0: Y=Y0
def draw_magasin():
    for j in range(5):
        for i in range(5):
            pygame.draw.rect(screen, WHITE, pygame.Rect(Xmin+i*120, Ymin+j*80, 120, 80),width=2)
            if LL[j][i]!='':
                font = pygame.font.SysFont("arial", 10)
                texte_x = pygame.font.Font.render(font, LL[j][i], True, (0,255,255))
                screen.blit(texte_x, dest=(Xmin+i*120+5, Ymin+(4-j)*80+5))
                
def aller_pos_desiree(Xd,Yd):
    global X, Y, deltaX, deltaY
    if X < Xd  :
            X=X+deltaX
            if X > Xd: X=Xd
        
    if Y > Yd :
            Y=Y+deltaY
            if Y < Yd: Y=Yd
    
LL=[]   # liste de listes contenant les ref des produits stockés
NC,NL=5,5       #magasin de 5 colonnes 5 lignes
for i in range(NC):
    L=[]
    for j in range(NL):
        L.append('') # au debut magasin vide aucun stock donc Ref ''
    LL.append(L)

#on initialise certaines cases avec des refs avec des refs
for i in range(5):
    for j in range(3):
        LL[i][j]="Ref"+str(i)+str(j)
for i in range(4):
    LL[i][3]="Ref"+str(i)+str(j)
for i in range(4):
    LL[i][4]="Ref"+str(i)+str(j)
# dans le magasin on a mnt des cases vides et des cases pleines
#limits de deplacement des barres
Xmin=40;Xmax=width  # barre verti
Ymin=0; Ymax=height-40  #barre horizontale
#vitesse de deplac: 2 pixels par 1/FRAMES_PER_SECOND =1/10sec
deltaX=2;deltaY=-2
#position de repos
X0,Y0=20,height-20  #barre vert à X0=20 et barre horiz à Y0=height-20
#init a la pos de repos des barres 
X=X0;Y=Y0

#trouver une case vide qui sera désignée par NumC,NumL numéros de colonne et de ligne
NumC, NumL=trouver_case_vide() #si ca return None aucune case vide
print(NumC,NumL)


#affichage en position initiale
draw_Barre_verticale(X0,height)
draw_Barre_horizontale(Y0,width)
pygame.display.update()
clock.tick(FRAMES_PER_SECOND)
#position desiree
#NumC entre 0 et 4 et NumL entre 0 et 4
#NumC=2; NumL=3
Xdesiree=NumC*120+Xmin+60
Ydesiree=(4-NumL)*80+Ymin+40 # NumL=0 en bas de l'ecran
compt=0 #compteur de temps

#au debut on fait un chargement d'un produit de Ref XR123 par exemple
#puis on fait un déchargement du meme produit
#et le cycle recommence
chargement=True  # tru --> chargement , false --> dechargement
Ref="XR123" # Ref contiendra la reference du produit à charger ou qui sera déchargé
Chariot=Ref #Chariot contient la ref du produit dans le chariot
while True:
    #print(LL)
    for event in pygame.event.get():
        if event.type == pygame.QUIT: sys.exit()
    #print(X,Y)
    if compt==0: #compt=0 debut du demi cycle on fait un chargement ou dechargement
        if chargement: Chariot=Ref  #chariot contient le produit
        else: Chariot="" #retour chariot vide
        aller_pos_desiree(Xdesiree,Ydesiree)
        
    if (X,Y) ==(Xdesiree, Ydesiree): #arrivee a pos desiree
        compt=compt+1 #temporisation par comptage
        #print(compt)
        if chargement and compt==1: #chargement
            LL[NumL][NumC]=Chariot ; Chariot='' #placement du produit à la case
        if not chargement and compt==1:#dechargement
            Chariot=LL[NumL][NumC]; LL[NumL][NumC]='' #retrait du produit de la case
        
    if compt==50: #fin de temporisation 50*1/FRAMES_PER_SECOND =50/10=5s
        #après 5sec retour à pos initiale
        retour_pos_init()
        if X==X0  and Y==Y0 :
            compt=-50
            if chargement: chargement=False #baseculment du chargement/dechargement
            else: chargement=True
    if compt<0:
        compt=compt+1 #tempo par comptage de -50 à 0
        #print(compt)
    
    screen.fill(BLACK) 
    draw_magasin()
    draw_Barre_verticale(X, height)
    draw_Barre_horizontale(Y,width)
    draw_chariot(X,Y,Chariot)
    pygame.display.update()
    clock.tick(FRAMES_PER_SECOND)

3 réponses

D'après toi, quels sont les objets que tu manipules?
Quels sont leurs attributs et/ou propriétés?

Pourquoi des variables globales?

C'est déjà un début de réflexion.

0

Ceci ne répond pas à ta question, mais en tant que "one-liner" invétéré, je n'ai pas pu résister.

Voici ton initialisation en simplifié:

NC,NL=5,5       #magasin de 5 colonnes 5 lignes
LL = [[""] * NC for _ in range(NL)]                                                                                   

Si vraiment seules les deux dernière colonnes de la dernière ligne sont vides, on peut faire plus simple.

0
desir_indecis
14 avril 2023 à 21:22

Un exemple amélioré de ton code lequel si j'ai bien saisi représente un magasin automatique à la manière du stockage d'un entrepôt.

import pygame as pg
import math
import random

FPS = 60
SCREEN_WIDTH = 660
SCREEN_HEIGHT = 460
SCREEN_COLOR = pg.Color(0, 0, 0, 0)

AREA_BG_COLOR = pg.Color(0, 0, 0, 0)
AREA_BORDER_COLOR = pg.Color(0, 255, 0, 0)

AREA_ADRESS_COLOR = pg.Color(0, 255, 255, 0)
AREA_PRODUCT_COLOR = pg.Color(255, 255, 0, 0)
AREA_QUANTITY_COLOR = pg.Color(0, 255, 255, 0)

CART_BAR_SIZE = 20
CART_BAR_BG_COLOR = pg.Color(0, 0, 255, 160)
CART_BAR_FG_COLOR = pg.Color(128, 128, 255, 255)

CURSOR_SIZE = CART_BAR_SIZE * 2
CURSOR_BG_COLOR = pg.Color(0, 0, 128)
CURSOR_FG_COLOR = pg.Color(128, 128, 128)
# CART_CURSOR_SPEED = 4
CART_CURSOR_SPEED = 12
# CART_TRANSFERT_SPEED = 5
CART_TRANSFERT_SPEED = 15
CART_WAIT_DELAY = 25


class Point:
    __slots__ = ('x', 'y')

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __str__(self):
        return f'x: {self.x}, y: {self.y}'


class Area(pg.sprite.Sprite):
    width = 120
    height = 80

    def __init__(self, x, y, adress):
        super().__init__()
        self.__adress = adress
        self.image = pg.Surface((self.width, self.height)).convert()
        self.rect = self.image.get_rect()
        self.image.fill(AREA_BORDER_COLOR)
        pg.draw.rect(self.image, AREA_BG_COLOR, self.rect.inflate(-2, -2))
        self.font = pg.font.SysFont('mono', 15)
        self.image.blit(
            self.font.render(self.__adress, True, AREA_ADRESS_COLOR), (3, 3)
        )
        self.rect.topleft = x, y
        self._product = None
        self._quantity = 0
        self._rect_product = pg.Rect(
            5, self.rect.height / 3, self.width - 10, self.height / 3 - 4
        )
        self._rect_quantity = pg.Rect(
            5, self.rect.h / 3 * 2, self.rect.w - 10, self.rect.h / 3 - 4
        )

    def _draw_product(self):
        self.image.fill(AREA_BG_COLOR, self._rect_product)
        text_surf = self.font.render(self._product, True, AREA_PRODUCT_COLOR)
        self.image.blit(text_surf, self._rect_product)

    def _draw_quantity(self):
        self.image.fill(AREA_BG_COLOR, self._rect_quantity)
        text_surf = self.font.render(
            str(self._quantity), True, AREA_QUANTITY_COLOR
        )
        self.image.blit(text_surf, self._rect_quantity)

    @property
    def adress(self):
        return self.adress

    @property
    def product(self):
        return self._product

    @property
    def quantity(self):
        return self._quantity

    def put(self, product, quantity):
        assert self._quantity == 0, "Can't change product, stock is not to 0"
        self._product = product
        self._quantity = quantity
        self._draw_product()
        self._draw_quantity()

    def pick(self, quantity):
        assert (
            self._quantity >= quantity
        ), f"Can't pick more than {self._quantity} units"
        self._quantity -= quantity
        self._draw_quantity()

    def add(self, quantity):
        # assert (
            # self._quantity + quantity > self._quantity_max
        # ), f"Can't contains more than {self._quantity_max} units"
        self._quantity += quantity
        self._draw_quantity()


class CartBar(pg.sprite.Sprite):
    def __init__(self, x, y, width, height):
        super().__init__()
        self.image = pg.Surface((width, height), pg.SRCALPHA).convert_alpha()
        self.rect = self.image.get_rect()
        self.image.fill(CART_BAR_BG_COLOR)
        self.rect.topleft = x, y
        self.font = pg.font.SysFont('mono', 15, True)


class HorizontalCartBar(CartBar):
    def __init__(self):
        y = SCREEN_HEIGHT - CURSOR_SIZE + CART_BAR_SIZE / 2
        super().__init__(0, y, SCREEN_WIDTH, CART_BAR_SIZE)
        self._label = ""

    def label(self, text):
        if self._label != text:
            self._label = text
            self.image.fill(CART_BAR_BG_COLOR)
            text = self.font.render(text.upper(), True, CART_BAR_FG_COLOR)
            rect = text.get_rect()
            y = self.rect.h / 2
            for x in (CURSOR_SIZE + 50, SCREEN_WIDTH - 50 - rect.w):
                self.image.blit(text, text.get_rect(midleft=(x, y)))


class VerticalCartBar(CartBar):
    def __init__(self):
        x = (CURSOR_SIZE - CART_BAR_SIZE) / 2
        super().__init__(x, 0, CART_BAR_SIZE, SCREEN_HEIGHT)
        self._label = ""

    def label(self, text):
        if self._label != text:
            self._label = text
            self.image.fill(CART_BAR_BG_COLOR)
            x = self.rect.w / 2
            text = self.font.render(text.upper(), True, CART_BAR_FG_COLOR)
            text = pg.transform.rotate(text, -90)
            rect = text.get_rect()
            for y in (
                CURSOR_SIZE + rect.h / 2,
                SCREEN_HEIGHT - CURSOR_SIZE - rect.h,
            ):
                self.image.blit(text, text.get_rect(center=(x, y)))


class CartCursor(pg.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pg.Surface(
            (CURSOR_SIZE, CURSOR_SIZE), pg.SRCALPHA
        ).convert()
        self.rect = self.image.get_rect()
        self.image.fill(CURSOR_BG_COLOR)
        self.rect.topleft = 0, SCREEN_HEIGHT - CURSOR_SIZE
        self.font = pg.font.SysFont('mono', 15, True)

    def quantity(self, quantity):
        self.image.fill(CURSOR_BG_COLOR)
        text = self.font.render(str(quantity), True, CURSOR_FG_COLOR)
        center = self.rect.w / 2, self.rect.h / 2
        self.image.blit(text, text.get_rect(center=center))


# Forklift
class Cart:
    def __init__(self, store, *groups):
        self.store = store
        self.hbar = HorizontalCartBar()
        self.vbar = VerticalCartBar()
        self.cursor = CartCursor()
        for group in groups:
            group.add(self.hbar, self.vbar, self.cursor)
        self._moving = False
        self._action = 0

    def _set_route(self):
        def get_points(start, end, step):
            dist = math.sqrt((start.x - end.x) ** 2 + (start.y - end.y) ** 2)
            numbers = int(dist / step)
            d = Point((end.x - start.x) / numbers, (end.y - start.y) / numbers)
            points = [
                Point(start.x + d.x * i, start.y + d.y * i)
                for i in range(numbers)
            ]
            points.append(end)
            return points

        dest = Point(*self.store.get_pos(self._product))
        self._route = get_points(
            self.cursor.rect, dest, CART_CURSOR_SPEED * (30 / FPS)
        )

    def _set_work(self, work, product, quantity):
        self._moving = True
        self._work = work
        self._product = product
        self._quantity = quantity
        self._i = 0
        self._action = 1
        self._set_route()
        self.cursor.quantity(self._quantity)
        self.hbar.label(self._product)
        self.vbar.label(self._work.__name__)

    def pick(self, product, quantity):
        if self._moving:
            raise RuntimeWarning("Can't pick product, cart is moving")
        self._set_work(self.store.pick, product, quantity)

    def put(self, product, quantity):
        if self._moving:
            raise RuntimeWarning("Can't put product, cart is moving")
        self._set_work(self.store.put, product, quantity)

    def update(self):
        if self._action in (1, 4):
            try:
                point = self._route[self._i]
                self.cursor.rect.topleft = point.x, point.y
                self.hbar.rect.y = point.y + 10
                self.vbar.rect.x = point.x + 10
                self._i += 1
            except IndexError:
                if self._action == 1:
                    self._i = 0
                    self._action = 2
                else:
                    self._action = 0
                    self._moving = False
        elif self._action == 2:
            self._i += 1
            if not self._i % int(CART_TRANSFERT_SPEED * (30 / FPS)):
                self._work(self._product, 1)
                self._quantity -= 1
                self.cursor.quantity(self._quantity)
                if not self._quantity:
                    self._action = 3
                    self._i = 0
                    self._route.reverse()
        elif self._action == 3:
            # délai d'attente
            self._i += 1
            if self._i == int(25 / (30 / FPS)):
                self._i = 0
                self._action = 4

    @property
    def moving(self):
        return self._moving


# Warehouse ?
class Store:
    def __init__(self, width, height, x, y, *groups):
        self.groups = groups
        self._stock = tuple(
            Area(
                x + i * (Area.width + 3),
                y + j * (Area.height + 3),
                f'{chr(65 + j)}{i + 1}',
            )
            for j in range(height)
            for i in range(width)
        )
        for group in groups:
            group.add(self._stock)

    def pick(self, product, quantity):
        for area in self._stock:
            if area.product == product:
                try:
                    area.pick(quantity)
                except AssertionError:
                    raise ValueError()
                return
        raise ValueError(f"product {product} not in stock")

    def put(self, product, quantity):
        for area in self._stock:
            if area.product == product:
                area.add(quantity)
                return
        for area in self._stock:
            if area.product is None:
                area.put(product, quantity)
                return
        for area in self._stock:
            if area.quantity == 0:
                area.put(product, quantity)
                return
        raise ValueError(f"Can't add product in stock, no free area")

    @property
    def products(self):
        return tuple(area.product for area in self._stock)

    def get_quantity(self, product):
        for area in self._stock:
            if area.product == product:
                return area.quantity
        raise ValueError(f"product {product} not in store")

    def get_avalaible_products(self):
        return tuple(area.product for area in self._stock if area.quantity)

    def get_pos(self, product):
        for area in self._stock:
            if area.product == product:
                return area.rect.midtop


class App:
    def __init__(self):
        pg.init()
        self.screen = pg.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
        self.screen.convert()
        self.screen.fill(SCREEN_COLOR)
        pg.display.update()
        self.display_group = pg.sprite.LayeredUpdates()
        self.store = Store(5, 5, 40, 5, self.display_group)
        for i in range(1, 26):
            quantity = random.randint(1, 20)
            self.store.put(f'produit {i}', quantity)
        self.cart = Cart(self.store, self.display_group)
        # Tests
        self.ACTION_EVENT = pg.USEREVENT
        pg.time.set_timer(self.ACTION_EVENT, 2000)

    def _random_action(self):
        work = (self.cart.pick, self.cart.put)[random.randrange(2)]
        if work == self.cart.pick:
            products = self.store.get_avalaible_products()
            product = random.choice(products)
            quantity = random.randint(1, min(self.store.get_quantity(product), 20))
        else:
            products = self.store.products
            product = random.choice(products)
            quantity = random.randint(1, 20)
        work(product, quantity)

    def _clear(self, surf, rect):
        surf.fill(SCREEN_COLOR, rect)

    def run(self):
        clock = pg.time.Clock()
        running = True
        while running:
            for evt in pg.event.get():
                if evt.type == pg.QUIT:
                    running = False
                elif evt.type == self.ACTION_EVENT:
                    if not self.cart.moving:
                        self._random_action()
            self.cart.update()
            self.display_group.update()
            self.display_group.clear(self.screen, self._clear)
            rects = self.display_group.draw(self.screen)
            pg.display.update(rects)
            clock.tick(FPS)

        pg.quit()

Pas mal de choses peuvent être améliorées, entre autres :
- Créer une (ou plusieurs) classe dérivée d'Exception afin de gérer les exceptions de l'application.
- Ajouter des méthodes à la class Store, par exemple une méthode donnant les produits en pénurie, une autre donnant le produit stocké à telle adresse, etc.
- Changer la couleur du chariot (barre, curseur) en fonction de l'action effectuée, alimentation ou prélèvement.
- Ajouter une quantité de stockage max. dans les zones, et gérer le cas lorsqu'il est tenté d'ajouter plus de marchandises que la zone peut reçevoir.
- Revoir la façon dont sont créées les zones, ie: s'arranger pour que ce soit plus configurable.
- Certainement revoir le nom des classes Store et Cart, après c'est sans doute plus simple à écrire que Warehouse et Forklift...
- Et de fil en aiguille, d'autres idées viennent, etc, etc.

0