(czyli: coś w końcu istnieje w świecie) 🌍
Autor: Asprocool | Kontakt: Asprocool@int.pl
Mamy już świat który liczy czas. Czas na pierwszy obiekt.
Jedna nowość: obiekt istnieje w przestrzeni. Ma pozycję. Można go ruszyć i zobaczyć.
| Co dodajemy | Typ | Po co |
|---|---|---|
class Object |
klasa | pierwszy byt w przestrzeni |
x, y |
atrybuty | pozycja obiektu |
history |
atrybut (lista) | pamięć poprzednich pozycji |
move_right(obj, step) |
funkcja | zmiana stanu — ruch w prawo |
move_up(obj, step) |
funkcja | zmiana stanu — ruch w górę |
draw_object(obj) |
funkcja | wizualizacja punktu |
draw_trajectory(obj) |
funkcja | wizualizacja trajektorii |
Potrzebujesz z L00:
class i konstruktor __init__self.atrybutmatplotlib i jupyterJeśli nie — wróć do L00, sekcja Instalacja środowiska.
Po tej lekcji:
x, y)Nadal nie mamy:
To jest czysta kinematyka — i bardzo dobrze.
W 1637 roku René Descartes opublikował La Géométrie — dodatek do słynnego Rozprawy o metodzie. Opisał w nim pomysł który dziś wydaje się oczywisty: każdy punkt w przestrzeni można opisać parą liczb (x, y).
Legenda mówi, że pomysł przyszedł mu do głowy gdy leżał w łóżku i obserwował muchę chodzącą po suficie. Uświadomił sobie, że może opisać dokładne położenie muchy podając odległość od dwóch ścian. Reszta to historia — układ współrzędnych który dziś nosi jego imię (kartezjański).
W naszej symulacji robimy dokładnie to samo: self.x i self.y to odległości od punktu (0, 0). Każdy obiekt w symulacji to punkt kartezjański — opisany dwiema liczbami.
🧠 Ironia: Descartes opisał muchę parą liczb w XVII wieku.
My opisujemy pocisk parą liczb w XXI wieku.
Matematyka się nie zmieniła.
Stan to komplet informacji opisujących obiekt w danej chwili.
Analogie:
W naszej symulacji minimalny stan to:
self.x = 0.0 # pozycja pozioma
self.y = 0.0 # pozycja pionowa
I NIC WIĘCEJ. Nie ma prędkości, nie ma masy, nie ma czasu. Tylko: gdzie obiekt jest teraz.
🧱 Zasada Profesora: Zaczynamy od najmniejszego sensownego stanu. Resztę dołożymy później.
class ObjectTak jak World był klasą — obiekt też jest klasą. Dlaczego?
x, y)World = zegarek świata — tyka i liczy czas
Object = coś w tym świecie — ma pozycję, można je ruszyć
Oba są klasami bo oba mają stan i zachowanie.
class Object:
def __init__(self, x, y):
self.x = x # pozycja pozioma
self.y = y # pozycja pionowa
class Object:
Nowy typ danych — obiekt symulacji. Od tej chwili Python wie co to Object.
def __init__(self, x, y):
Konstruktor — uruchamia się automatycznie gdy piszesz Object(x=0.0, y=0.0). Dostaje dwa argumenty: x i y — początkową pozycję.
self.x = x # pozycja pozioma
self.y = y # pozycja pionowa
Zapisujemy stan w obiekcie. self.x to atrybut — własna zmienna tego konkretnego obiektu. To jest cały stan obiektu w tej lekcji. Tylko dwie liczby.
| Błąd | Co się dzieje | Jak naprawić |
|---|---|---|
Object(0, 0) zamiast Object(x=0.0, y=0.0) |
Oba działają — ale z nazwami jest czytelniej | Oba są OK |
print(obj) |
Wypisuje adres w pamięci, nie wartości | Użyj print(obj.x, obj.y) |
Zmieniasz x zamiast obj.x |
Tworzysz nową zmienną lokalną, obiekt się nie zmienia | Zawsze obj.x = ... |
Obiekt istnieje. Nie rusza się. Nie wie że istnieje świat. Ale ma stan.
# Najpierw potrzebujemy definicji klasy Object (uruchom komórkę powyżej)
obj = Object(x=0.0, y=0.0)
print(obj.x, obj.y)
obj = Object(x=0.0, y=0.0)
Tworzymy konkretny egzemplarz klasy Object z pozycją (0, 0). Od tej chwili obj to nasz obiekt — ma własne obj.x i obj.y.
print(obj.x, obj.y)
Odczytujemy stan obiektu. obj.x to pozioma pozycja, obj.y to pionowa.
🎉 Gratulacje — obiekt istnieje!
Obiekt sam z siebie się nie porusza.
Żeby go ruszyć, musimy zmienić jego stan — ręcznie, jawnie, bez fizyki.
Wyobraź sobie szachownicę. Pionek nie rusza się sam. Ktoś (gracz, reguły gry, AI) decyduje o ruchu i zmienia pozycję.
W naszej symulacji tym „kimś" na razie jest funkcja:
def move_right(obj, step):
obj.x += step
move_right(obj, 1.0)def move_right(obj, step):
obj.x += step # zwiększ pozycję poziomą o 'step'
def move_up(obj, step):
obj.y += step # zwiększ pozycję pionową o 'step'
# Test — ruch w prawo
move_right(obj, 1.0)
print("Po move_right(1.0): ", obj.x, obj.y)
# Ruch w górę
move_up(obj, 0.5)
print("Po move_up(0.5): ", obj.x, obj.y)
def move_right(obj, step):
obj.x += step
Funkcja przyjmuje obiekt i krok ruchu. obj.x += step to skrót od obj.x = obj.x + step. Zmienia stan obiektu — x rośnie o step.
🧠 Ważne: Ruch = zmiana stanu w czasie.
Nie: animacja, grafika, fizyka.
Tylko: liczbaxsię zmieniła.
move_right(obj, 1.0)
Wywołujemy funkcję — obiekt przesuwa się o 1.0 w prawo. obj.x było 0.0, teraz jest 1.0.
| Błąd | Co się dzieje | Jak naprawić |
|---|---|---|
move_right(1.0) bez obj |
TypeError: missing argument |
Zawsze podaj obiekt jako pierwszy argument |
obj.x = step zamiast += |
Ustawia pozycję na step, nie dodaje |
Użyj += |
Tworzysz x = obj.x + step ale nie przypisujesz do obj.x |
Obiekt się nie porusza | obj.x = obj.x + step lub obj.x += step |
Teraz musimy to zobaczyć.
plt.scatter() vs plt.plot()W L00 używaliśmy plt.plot() — rysuje linię łączącą punkty.
Tu używamy plt.scatter() — rysuje niezależne punkty, bez łączenia.
Dlaczego? Bo w tej lekcji obiekt jest w jednym miejscu — nie ma jeszcze trajektorii ani ruchu jako procesu. scatter mówi: „to jest obserwacja stanu", nie „to jest przebieg w czasie".
plt.scatter(x, y, s=100) # s = rozmiar punktu w pikselach²
plt.xlim(-5, 5) # zakres osi X
plt.ylim(-5, 5) # zakres osi Y
plt.grid() # siatka pomocnicza
Wykres z jednym czerwonym punktem. Oś X od -5 do 5, oś Y od -5 do 5. Po move_right(obj, 2) punkt przesunie się w prawo.
import matplotlib.pyplot as plt
import csv
# ======================
# Klasa Object z historią
# ======================
class Object:
def __init__(self, x, y):
self.x = x
self.y = y
self.history = [(x, y)] # pamięć pozycji — zaczyna od punktu startowego
def save(self):
"""Zapisz aktualną pozycję do historii."""
self.history.append((self.x, self.y))
# ======================
# Funkcje ruchu
# ======================
def move_right(obj, step):
"""Przesuń obiekt w prawo o step."""
obj.x += step
obj.save() # zapamiętaj nową pozycję
def move_up(obj, step):
"""Przesuń obiekt w gore o step."""
obj.y += step
obj.save()
# ======================
# Funkcje wizualizacji
# ======================
def draw_object(obj, title="Obiekt w przestrzeni"):
"""Rysuje aktualną pozycję obiektu jako punkt."""
plt.figure(figsize=(6, 6))
plt.scatter(obj.x, obj.y, s=150, color='red', zorder=5)
plt.xlim(-5, 5)
plt.ylim(-5, 5)
plt.axhline(0, color='gray', linewidth=0.5)
plt.axvline(0, color='gray', linewidth=0.5)
plt.xlabel("x")
plt.ylabel("y")
plt.title(title)
plt.grid(True, alpha=0.3)
plt.show()
def draw_trajectory(obj, title="Trajektoria obiektu"):
"""Rysuje historię pozycji obiektu jako ścieżkę."""
xs = [p[0] for p in obj.history]
ys = [p[1] for p in obj.history]
plt.figure(figsize=(6, 6))
plt.plot(xs, ys, 'b-', linewidth=1.5, alpha=0.5) # linia łącząca
plt.scatter(xs, ys, s=80, color='red', zorder=5) # punkty
plt.scatter(xs[0], ys[0], s=150, color='green', zorder=6, label='start')
plt.scatter(xs[-1], ys[-1], s=150, color='blue', zorder=6, label='koniec')
plt.xlim(-6, 6)
plt.ylim(-6, 6)
plt.axhline(0, color='gray', linewidth=0.5)
plt.axvline(0, color='gray', linewidth=0.5)
plt.xlabel("x")
plt.ylabel("y")
plt.title(title)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# ======================
# Symulacja — kilka kroków ruchu
# ======================
obj = Object(x=0.0, y=0.0)
move_right(obj, 1.0)
move_right(obj, 1.0)
move_up(obj, 0.5)
move_right(obj, 0.5)
move_up(obj, 1.0)
print("Historia pozycji: ", obj.history)
print(f"Pozycja końcowa: x={obj.x}, y={obj.y}")
draw_trajectory(obj)
# ======================
# 💾 Export do CSV
# ======================
# Co robi ten blok:
#
# open('lekcja_01.csv', 'w', ...)
# → tworzy plik lekcja_01.csv w folderze notebooka
#
# writer.writerow(['krok', 'x', 'y'])
# → nagłówek: krok,x,y
#
# enumerate(obj.history)
# → daje pary (numer, (x, y)): (0,(0,0)), (1,(1,0)), ...
#
# writer.writerow([i, round(x, 6), round(y, 6)])
# → jeden wiersz: np. "2,2.0,0.5"
with open('lekcja_01.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['krok', 'x', 'y'])
for i, (x, y) in enumerate(obj.history):
writer.writerow([i, round(x, 6), round(y, 6)])
print(f"\n✅ Zapisano {len(obj.history)} pozycji do lekcja_01.csv")
print(" Zawartość pliku:")
print(" krok,x,y")
for i, (x, y) in enumerate(obj.history):
print(f" {i},{round(x,6)},{round(y,6)}")
self.history = [(x, y)]
Lista krotek — każda krotka to (x, y) w danym kroku. Zaczyna od pozycji startowej. (0.0, 0.0) jeśli obiekt zaczyna w środku.
def save(self):
self.history.append((self.x, self.y))
Po każdym ruchu wywołujemy save() — dodajemy aktualną pozycję do historii. Dzięki temu obiekt pamięta skąd przyszedł.
xs = [p[0] for p in obj.history]
ys = [p[1] for p in obj.history]
List comprehension — wyciągamy wszystkie współrzędne X i Y z historii. p[0] = pierwsza wartość krotki = X, p[1] = druga = Y.
plt.scatter(xs[0], ys[0], color='green', label='start')
plt.scatter(xs[-1], ys[-1], color='blue', label='koniec')
xs[0] = pierwszy element = punkt startowy (zielony).
xs[-1] = ostatni element = punkt końcowy (niebieski).
-1 to indeks ostatniego elementu listy w Pythonie.
for i, (x, y) in enumerate(obj.history):
writer.writerow([i, round(x, 6), round(y, 6)])
enumerate daje pary (numer, wartość). (x, y) to unpacking krotki — rozpakowujemy (1.0, 0.5) na x=1.0 i y=0.5.
💡 Jak uruchamiać eksperymenty?
Uruchom najpierw główną komórkę z kodem. Potem wklej każdy eksperyment do nowej komórki.
Co robisz:
obj2 = Object(x=0.0, y=0.0)
move_up(obj2, 1.0)
move_up(obj2, 1.0)
move_up(obj2, 1.0)
draw_trajectory(obj2, title="Ruch w górę")
Co zobaczysz: Pionowa linia z trzech punktów. Start na (0,0), koniec na (0,3).
Szczegółowe wyjaśnienie: Każde move_up zwiększa y o 1.0. x się nie zmienia — zostaje 0.0. Historia: [(0,0), (0,1), (0,2), (0,3)]. Wykres pokazuje punkty ułożone pionowo.
Dlaczego to ważne: Ruch w jednym kierunku = zmiana tylko jednej współrzędnej. W L02 prędkość będzie zmieniać obie współrzędne jednocześnie.
Co robisz:
obj3 = Object(x=0.0, y=0.0)
for _ in range(5):
move_right(obj3, 0.8)
move_up(obj3, 0.6)
draw_trajectory(obj3, title="Ruch po skosie")
print("Pozycja końcowa:", obj3.x, obj3.y)
Co zobaczysz: Ukośna linia z 6 punktów (start + 5 ruchów). Koniec w okolicach (4, 3).
Szczegółowe wyjaśnienie: Każdy krok: x += 0.8, y += 0.6. Historia rośnie o 2 wpisy na iterację pętli. Czemu (4, 3) a nie (4.0, 3.0)? Bo 5 × 0.8 = 4.0 i 5 × 0.6 = 3.0.
Dlaczego to ważne: Ruch po skosie = zmiana obu współrzędnych. To zapowiedź wektora prędkości w L02.
Co robisz:
a = Object(x=-2.0, y=0.0)
b = Object(x=2.0, y=0.0)
for _ in range(3):
move_right(a, 0.5)
move_up(b, 0.8)
# Narysuj oba na jednym wykresie
plt.figure(figsize=(7, 7))
for obj_plot, color, label in [(a, 'red', 'Obiekt A'), (b, 'blue', 'Obiekt B')]:
xs = [p[0] for p in obj_plot.history]
ys = [p[1] for p in obj_plot.history]
plt.scatter(xs, ys, color=color, s=100, label=label)
plt.plot(xs, ys, color=color, alpha=0.4)
plt.xlim(-5, 5); plt.ylim(-5, 5)
plt.grid(True, alpha=0.3); plt.legend(); plt.title("Dwa obiekty")
plt.show()
Co zobaczysz: Dwie osobne ścieżki — czerwona idzie w prawo, niebieska w górę.
Szczegółowe wyjaśnienie: Każdy obiekt ma własne x, y i history. Zmiana a.x nie wpływa na b.x — to dwie niezależne instancje klasy Object. To właśnie po co używamy klas: każdy obiekt ma swój własny stan.
Dlaczego to ważne: W L21+ będziemy mieć N dział jako N obiektów — każde z własną historią.
Co robisz:
# Czy obiekt "pamięta" że istnieje bez ruchu?
stojak = Object(x=3.0, y=-1.5)
print("Pozycja:", stojak.x, stojak.y)
print("Historia:", stojak.history)
print("Liczba kroków:", len(stojak.history))
# Spróbuj odczytać pozycję 10 razy bez żadnego ruchu
for i in range(10):
print(f"Odczyt {i}: x={stojak.x}, y={stojak.y}")
Co zobaczysz:
Szczegółowe wyjaśnienie: Obiekt istnieje bez ruchu — ma stan od chwili stworzenia. Historia ma 1 element — punkt startowy, zapisany w __init__. 10 odczytów daje 10 × te same wartości — stan się nie zmienił.
Dlaczego to ważne: Istnienie ≠ ruch. Obiekt istnieje przez stan, nie przez ruch. To jest zasada tej lekcji: obiekt istnieje przez stan, nie przez ruch.
| Element | Typ | Atrybuty | Metody / Funkcje | Co robi |
|---|---|---|---|---|
World |
klasa | dt, time, history |
step() |
zarządza czasem (z L00) |
Object |
klasa | x, y, history |
save() |
punkt w przestrzeni 2D |
move_right(obj, step) |
funkcja | — | — | przesuwa obiekt w prawo |
move_up(obj, step) |
funkcja | — | — | przesuwa obiekt w górę |
draw_object(obj) |
funkcja | — | — | rysuje aktualną pozycję |
draw_trajectory(obj) |
funkcja | — | — | rysuje historię pozycji |
Jeszcze nie mamy:
| Element | Kiedy |
|---|---|
prędkość vx, vy w Object |
L02 |
funkcja update(obj, world) — jeden krok fizyki |
L02 |
class Influence, Gravity, Drag |
L06 |
class Cannon, Projectile, fire() |
L11 |
class Replay |
L18 |
class GameRules, GameLoop |
L23–L24 |
Object ma pozycję ale nie ma prędkości. W L02 dodamy vx, vy — prędkość. Ruch przestanie być ręczny (move_right), a stanie się automatyczny: każdy krok czasu (step()) zmienia pozycję o prędkość. To jest przejście od kinematyki do dynamiki.
Po tej lekcji mamy:
Object — punkt w przestrzeni 2Dx, y — stan pozycjihistory — pamięć pozycjimove_right(), move_up() — zmiana stanudraw_object(), draw_trajectory() — wizualizacja👉 Lekcja 02 — Ruch w czasie
Dodamy:
vx, vy w Objectupdate(obj, world) — jeden krok fizyki🧠 Złota myśl:
Ruch to zmiana stanu.
Ale najpierw musi istnieć coś, co można zmienić.
| Rozkaz | Co robi | Przykład |
|---|---|---|
self.history = [(x, y)] |
lista krotek jako historia | [(0.0, 0.0)] |
self.history.append((x, y)) |
dodaje krotkę do historii | obj.history.append((1.0, 0.5)) |
(x, y) |
krotka — para wartości | (3.0, -1.5) |
xs = [p[0] for p in history] |
list comprehension — wyciąga kolumnę | [0.0, 1.0, 2.0] |
for i, (x, y) in enumerate(...) |
unpacking krotki w pętli | i=0, x=0.0, y=0.0 |
plt.scatter(x, y, s=100) |
rysuje punkt (nie linię) | plt.scatter(2, 3, s=150) |
plt.axhline(0) |
pozioma linia pomocnicza (oś X) | plt.axhline(0, color='gray') |
plt.axvline(0) |
pionowa linia pomocnicza (oś Y) | plt.axvline(0, color='gray') |
plt.legend() |
pokazuje legendę | potrzebuje label= w plt.scatter/plot |
xs[-1] |
ostatni element listy | history[-1] = ostatnia pozycja |
lista = [1, 2, 3] # można zmieniać: lista[0] = 99
krotka = (1, 2, 3) # niemutowalna: nie można zmienić elementu
pozycja = (3.0, -1.5) # krotka — dobra do przechowywania pary (x, y)
x = pozycja[0] # → 3.0
y = pozycja[1] # → -1.5
# Unpacking — wygodny skrót:
x, y = pozycja # x=3.0, y=-1.5
historia = [(0,0), (1,0), (2,0), (3,0)]
# Bez list comprehension:
xs = []
for p in historia:
xs.append(p[0])
# Z list comprehension (krócej):
xs = [p[0] for p in historia # → [0, 1, 2, 3]
| Co | Jak | Uwaga |
|---|---|---|
| Utwórz obiekt | obj = Object(x=0.0, y=0.0) |
zaczyna z historią [(0,0)] |
| Odczytaj pozycję | obj.x, obj.y |
aktualna pozycja |
| Ruch w prawo | move_right(obj, 1.0) |
x += 1.0, zapisuje do history |
| Ruch w górę | move_up(obj, 0.5) |
y += 0.5, zapisuje do history |
| Wyświetl punkt | draw_object(obj) |
jeden punkt na wykresie |
| Wyświetl ścieżkę | draw_trajectory(obj) |
cała historia jako linia |
| Cała historia | obj.history |
lista krotek [(x,y), ...] |
| Ostatnia pozycja | obj.history[-1] |
krotka (x, y) |
| Liczba kroków | len(obj.history) - 1 |
-1 bo start się liczy |
| Kolumny X i Y | [p[0] for p in obj.history] |
list comprehension |
Sprawdź, czy naprawdę rozumiesz:
Object posiada atrybuty x i y?x i y opisują aktualną pozycję obiektu?history to lista krotek (x, y) — jedna na każdy krok?save() dodaje aktualną pozycję do history?move_right() zmienia x, a nie tworzy nowego obiektu?World?plt.scatter() od plt.plot()?move_left(obj, step)?xs[-1] zwraca ostatni element listy?a i b mają niezależne stany?Masz klasę Object z atrybutami x i y.
Zrób to w Jupyter:
obj_a = Object(x=1.0, y=2.0)
obj_b = Object(x=1.0, y=2.0)
print("Czy mają ten sam stan?", obj_a.x == obj_b.x and obj_a.y == obj_b.y)
print("Czy to ten sam obiekt?", obj_a is obj_b)
x i y należą do obiektu czy do świata?move_right() jest częścią stanu?Zrób to w Jupyter:
obj = Object(x=0.0, y=0.0)
# Wywołaj move_right 5 razy
for i in range(5):
move_right(obj, 0.5)
print(f"Krok {i+1}: x={obj.x}")
print("Historia:", obj.history)
Masz:
x, y — danemove_right(), move_up() — operacjeZrób to w Jupyter:
obj = Object(x=0.0, y=0.0)
# Czy dane zmienią się bez wywołania operacji?
print("Przed:", obj.x)
# (Nie rób nic)
print("Po:", obj.x)
# A teraz z operacją:
move_right(obj, 3.0)
print("Po move_right:", obj.x)
Napisz i przetestuj:
def move_left(obj, step):
# uzupełnij
def move_down(obj, step):
# uzupełnij
def move_diagonal(obj, step_x, step_y):
# uzupełnij — ruch po skosie
Narysuj kwadrat: prawo → góra → lewo → dół → punkt startowy.
obj = Object(x=0.0, y=0.0)
original_id = id(obj)
for _ in range(10):
move_right(obj, 1.0)
print("Ten sam obiekt?", id(obj) == original_id)
print("Pozycja końcowa:", obj.x)
id() mówi o tożsamości obiektu w Pythonie?Wyobraź sobie klasę Object bez x i y:
class ObjectBezStanu:
def move_right(self, step):
pass # co tu miałoby się zmienić?
move_right() mogłoby cokolwiek zmienić?Następnie udowodnij swoją odpowiedź kodem.
Object ma x i y, tworzone w __init__ jako self.x = x, self.y = y.x i y to aktualny stan — gdzie obiekt jest teraz. Nie historia, nie przyszłość.history to lista krotek [(x, y), ...]. Zaczyna od [(x_start, y_start)], rośnie po każdym save().save() wykonuje self.history.append((self.x, self.y)) — dokłada aktualną pozycję na koniec listy.move_right() modyfikuje self.x istniejącego obiektu. obj = Object(...) tworzy raz, potem tylko modyfikujemy.vx, vy. Prędkość pojawi się w L02.Object nie zna World. Nie ma atrybutu world ani time. To celowe — separacja odpowiedzialności.scatter rysuje niezależne punkty (obserwacje stanu). plot łączy punkty linią (przebieg w czasie). W L01 nie mamy jeszcze procesu w czasie — tylko punkty.def move_left(obj, step): obj.x -= step; obj.save() — minus zamiast plus.lista[-1] w Pythonie to ostatni element. lista[-2] to przedostatni. Działa dla każdej listy.xs = [p[0] for p in obj.history] — list comprehension wyciąga pierwszą wartość każdej krotki.Object ma własne x, y, history. a.x = 5 nie zmienia b.x.obj_a i obj_b mają ten sam stan (x=1.0, y=2.0) ale to różne obiekty w pamięci (obj_a is obj_b → False).
x i y należą do obiektu — są jego atrybutami, nie globalnymi zmiennymi.
move_right() to operacja, nie stan — stan to tylko dane (x, y).
Po 5 ruchach historia ma 6 elementów: [(0,0), (0.5,0), (1.0,0), ..., (2.5,0)] — punkt startowy + 5 kroków.
Prędkość nie jest potrzebna — pozycja zmienia się bezpośrednio przez x += step.
W L02 prędkość będzie automatycznie zmieniać pozycję w każdym step() świata.
Dane nie zmienią się bez wywołania funkcji — obj.x pozostaje 0.0 dopóki nic nie wywoła move_right().
Operacja bez danych nie ma sensu — move_right() musi mieć coś do zmiany (obj.x).
def move_left(obj, step):
obj.x -= step
obj.save()
def move_down(obj, step):
obj.y -= step
obj.save()
def move_diagonal(obj, step_x, step_y):
obj.x += step_x
obj.y += step_y
obj.save()
# Kwadrat:
obj = Object(x=0.0, y=0.0)
move_right(obj, 2.0)
move_up(obj, 2.0)
move_left(obj, 2.0)
move_down(obj, 2.0)
draw_trajectory(obj, title="Kwadrat")
Po 10 ruchach id(obj) == original_id → True — to ten sam obiekt w pamięci.
Zmienia się x (i history) — pozostaje tożsamość obiektu (adres w pamięci).
id() zwraca unikalny identyfikator obiektu. Modyfikacja atrybutów nie tworzy nowego obiektu.
Bez x i y nie można opisać położenia — nie ma żadnej wartości która by je reprezentowała.
move_right() nie miałoby co zmieniać — brak danych = brak stanu.
Absolutne minimum: przynajmniej jeden atrybut przechowujący dane.
class ObjectBezStanu:
def move_right(self, step):
pass # nie ma self.x — nic się nie zmienia
obj = ObjectBezStanu()
obj.move_right(5.0)
print(obj.x) # → AttributeError: 'ObjectBezStanu' has no attribute 'x'
Obiekt istnieje przez stan, nie przez ruch.
Ruch bez stanu to funkcja bez danych — pusty gest.
Obiekt istnieje przez stan, nie przez ruch.
Możesz mieć obiekt który nigdy się nie poruszy — i to jest poprawny obiekt.
Nie możesz mieć ruchu bez obiektu który ma stan.
Najpierw co istnieje, potem jak się zmienia.
Ruch to zmiana stanu.
Ale najpierw musi istnieć coś, co można zmienić.
📚 Koniec Lekcji 01
Następna lekcja: Lekcja 02 — Ruch w czasie
Autor: Asprocool | Kontakt: Asprocool@int.pl