image image


image

Py5 entdecken: Schaun das Schaf mit Py5 und Thonny

Wie am Freitag schon angekündigt, hatte ich mir für meine Erkundungen von Py5, dem/einen Python3-Mode von Processing.py ein letztes Beispiel aus Peter Farrells Buch »Math Adventures with Python« vorgenommen. Es ist das »Crazy Sheep«-Programm, eine nette kleine Simulation, die ich – in Abwandlungen – unter dem Titel »Shaun das Schaf und seine Spießgesellen« 2019 schon einmal in Processing.py vorgestellt hatte.

Bis auf wenige Änderungen (unter anderem die Nutzung meiner PVector-Klasse, Quellcode auf GitHub) entspricht der Py5-Port der Processing.py-Version, so daß ich auf die Einzelheiten der Implementierung nicht noch einmal eingehen möchte, Interessierte können alles hier nachlesen. Ich habe mich dort schließlich schon ausführlichst ausgemährt. Daher hier erst einmal der vollständige Quellcode, den ich auf vier (mit pyvector.py sogar fünf) Dateien verteilt habe.

Als erstes die Datei setting.py, die nur ein paar Konstanten und Parameter enthält, die Ihr bei Bedarf ändern könnt:

# Settings
import py5

# General

WIDTH = 640
HEIGHT = 480
NO_SHEEPS = 40
FPS = 5 # 60 for fast motion, 5 for slow motion

# Colors
WHITE = py5.color(255)
BROWN = py5.color(102, 51, 0)
RED = py5.color(250, 0, 0)
GREEN = py5.color(0, 102, 0)
YELLOW = py5.color(250, 204, 0)
BLUE = py5.color(0, 250, 250)
PURPLE = py5.color(150, 0, 250)

patch_size = 10 # Size of each patch of grass

Dann die Klasse Sheep, die die Blaupause für die Schafe liefert:

from pvector import PVector
import settings as s
import py5

class Sheep:
    
    def __init__ (self, x, y, col):
        self.pos = PVector(x, y)
        self.sz = 5      # Size
        self.move = 10
        self.energy = 20 # Energy level
        self.col = col
        
    def update(self):
        
        self.energy -= 1 # Walking costs energy
        self.pos.x += py5.random(-self.move, self.move)
        self.pos.y += py5.random(-self.move, self.move)
        self.check_boundaries()
        if self.energy >= 50:
            self.sz = 10
        elif self.energy >= 40:
            self.sz = 9
        elif self.energy >= 30:
            self.sz = 8
        elif self.energy >= 20:
            self.sz = 7
        elif self.energy >= 10:
            self.sz = 6
        else:
            self.sz = 5
        
    def check_boundaries(self):
        if self.pos.x >= s.WIDTH - self.sz/2:
            self.pos.x = s.WIDTH - self.sz/2
        if self.pos.x <= self.sz/2:
            self.pos.x = self.sz/2
        if self.pos.y >= s.HEIGHT - self.sz/2:
            self.pos.y = s.HEIGHT - self.sz/2
        if self.pos.y <= self.sz/2:
            self.pos.y = self.sz/2
        
    
    def show(self):
        py5.fill(self.col)
        py5.circle(self.pos.x, self.pos.y, self.sz)

Dort wurde – als weitere Änderung – die Größe der Schafe (Kreise) ihrem Energiezustand angepaßt: Viel Energie gleich große Kreise, wenig Energie gibt entsprechend kleinere Kreise.

Natürlich bekamen auch die Gras-Patches ihre eigene Klasse Grass:

from pvector import PVector
import settings as s
import py5

class Grass:
    
    def __init__(self, x, y, sz):
        self.pos = PVector(x, y)
        self.energy = 3    # Energy from eating this patch
        self.eaten = False # Hasn't been eaten yet
        self.sz = sz       # Patch size
    
    def update(self):
        if self.eaten:
            if py5.random_int(250) < 5:
                self.eaten = False  # Regeneration
            else:
                py5.fill(s.BROWN)
        else:
            py5.fill(s.GREEN)
        
    def show(self):
        py5.rect(self.pos.x, self.pos.y, self.sz, self.sz)

Und last but not least das Hauptprogramm, das ich unter shaun.py abgespeichert habe:

# Shaun das Schaf und seine Spießgesellen
# nach Peter Farrell »Math Adventures in Python«, p168ff
from sheep import Sheep
from grass import Grass
import settings as s

sheeps = [] # List to store sheeps
grasses = [] # List to store grass patches
colors = [s.WHITE, s.RED, s.BLUE, s.YELLOW]


def setup():
    size(s.WIDTH, s.HEIGHT)
    global row_of_grass
    row_of_grass = height/s.patch_size
    frame_rate(s.FPS)
    # Initialize a list with sheeps
    for _ in range(s.NO_SHEEPS):
        sheeps.append(Sheep(random_int(50, width - 50),
                            random_int(50, height - 50), random_choice(colors)))
    # Create the grass
    for x in range(0, width, s.patch_size):
        for y in range(0, height, s.patch_size):
            grasses.append(Grass(x, y, s.patch_size))

def draw():
    background(s.WHITE)
    for grass in grasses:
        grass.update()
        grass.show()
    for sheep in sheeps:
        sheep.update()
        xscl = int(sheep.pos.x/s.patch_size)
        yscl = int(sheep.pos.y/s.patch_size)
        grass = grasses[int(xscl*row_of_grass + yscl)]
        if not grass.eaten:
            sheep.energy += grass.energy
            grass.eaten = True
        if sheep.energy <= 0:
            sheeps.remove(sheep)
        if sheep.energy >= 50:
            sheep.energy -= 30  # Giving birth takes energy
            # Add a new sheep to the list
            sheeps.append(Sheep(sheep.pos.x, sheep.pos.y, sheep.col))
            
        sheep.show()
        # print("Anzahl der Schafe: " + str(len(sheeps)))

Den kompletten Quellcode findet Ihr natürlich auch in meinem GitHub-Repositorium.

Lessons learned

  1. Dateien, die nicht direkt von dem Thonny-Plugin von Tristan Bunn aufgerufen werden (also alle außer dem Hauptprogramm) müssen – wenn sie Py5-Funktionen aufrufen – py5 explizit importieren und mit dem Prefix py5. aufrufen. Das entspricht dem module mode von Py5 und funktioniert im Gegensatz zum Hauptprogramm auf meinem Mac erstaunlich gut.

    Natürlich läßt dies die Frage offen, ob es wirklich geschickt sei, das Programm in so viele kleine Einzeldateien aufzuteilen. In Processing.py klappt das ja sehr gut, da die einzelnen Tabs im Endeffekt zu einer großen Java-Datei verwurstet werden, die im gleichen Namespace liegt und die alles voneinander wissen. Das ist aber in Python nicht der Fall.

  2. Da die einzelnen Dateien nichts voneinander wissen, können die Klassen nicht implizit Informationen untereinander austauschen.

  3. Das führt zu meiner Überlegung, in Zukunft Klassen doch eher im Hauptprogramm zu deklarieren, da kennen sie sich (das ist zum Beispiel in Pygame Zero so etwas wie ein Standard).

  4. size() muß in Py5 immer an erster Stelle der setup()-Funktion stehen, also auch vor einem möglichen global-Statement.

Wenn ich mir was wünschen dürfte …

Nachdem der Schöpfer von Py5 auf meinen feature request nach der Aufnahme der mehr pythonischen with function() so positiv reagiert hatte, bin ich gleich unverschämt und melde weitere Wünsche an:

  1. Die Implementierung von Processings PVector-Klasse in Py5, weil sie – im Gegensatz zu meiner eigenen Implementierung – nicht nur zwei-, sondern auch dreidimensionale Vektoren behandeln kann.

  2. Eine Möglichkeit, dem Sketch-Fenster einen Titel zu geben. Ich weiß, Processing.py’s this.surface.setTitle() ist auch nur ein übler Hack, aber ich liebe nun einmal Titel im Programmfenster.

Weitere Wünsche werden mir bestimmt noch einfallen. 🤓 Jetzt geht es mir aber erst einmal darum, zu schauen, wie Py5 mit NumPy und anderen numerischen Modulen spielt. Denn da verlasse ich die sicheren Gefilde von Processing.py. Still digging!


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!

Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.

Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.

Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.


Werbung

Diese Spalte wurde absichtlich leergelassen!


Werbung


image  image  image
image  image  image


image