📓 Lekcja 01 — Obiekt i stan

(czyli: coś w końcu istnieje w świecie) 🌍

Autor: Asprocool | Kontakt: Asprocool@int.pl

📍 Gdzie jesteśmy?

L00 ✅ → L01 ✅ → L02 → L03 → ... → L20 → L21 → L22 → L23 → L24 → 🤖 AI

Mamy już świat który liczy czas. Czas na pierwszy obiekt.


🔄 Co nowego w tej lekcji

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

⚠️ Zanim zaczniesz

Potrzebujesz z L00:

Jeśli nie — wróć do L00, sekcja Instalacja środowiska.

🎯 Cel lekcji

Po tej lekcji:

Nadal nie mamy:

To jest czysta kinematyka — i bardzo dobrze.

🏛️ Anegdota historyczna

Kartezjusz i punkt w przestrzeni

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.

🧱 Pojęcie nr 1 — stan obiektu

💡 Dla nowicjusza

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.

🧱 Pojęcie nr 2 — class Object

Tak jak World był klasą — obiekt też jest klasą. Dlaczego?

💡 Dla nowicjusza

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.

Definicja klasy Object
class Object:
    def __init__(self, x, y):
        self.x = x   # pozycja pozioma
        self.y = y   # pozycja pionowa

🔍 Jak działa ten kod — linia po linii

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.

❓ Częste błędy

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 = ...

🧪 Tworzymy obiekt

▶️ Oczekiwany wynik

0.0 0.0

Obiekt istnieje. Nie rusza się. Nie wie że istnieje świat. Ale ma stan.

Tworzenie obiektu
# Najpierw potrzebujemy definicji klasy Object (uruchom komórkę powyżej)
obj = Object(x=0.0, y=0.0)
print(obj.x, obj.y)

🔍 Jak działa ten kod

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!

🧱 Pojęcie nr 3 — zmiana stanu

Obiekt sam z siebie się nie porusza.

Żeby go ruszyć, musimy zmienić jego stan — ręcznie, jawnie, bez fizyki.

💡 Dla nowicjusza

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

▶️ Oczekiwany wynik po move_right(obj, 1.0)

1.0 0.0
Funkcje ruchu
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)

🔍 Jak działa ten kod — linia po linii

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: liczba x się 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.

❓ Częste błędy

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

📊 Wizualizacja — punkt w 2D

Teraz musimy to zobaczyć.

💡 Dla nowicjusza — 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

▶️ Oczekiwany wynik

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.

Kompletny kod z wizualizacją i exportem CSV
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)}")

🔍 Jak działa ten kod — linia po linii

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.

🧪 Eksperymenty

💡 Jak uruchamiać eksperymenty?
Uruchom najpierw główną komórkę z kodem. Potem wklej każdy eksperyment do nowej komórki.


🟢 Eksperyment 1 — ruch w górę

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.


🟢 Eksperyment 2 — ruch po skosie

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.


🟡 Eksperyment 3 — porównaj dwa obiekty

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ą.


🔴 Eksperyment 4 — stan bez ruchu

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:

Pozycja: 3.0 -1.5 Historia: [(3.0, -1.5)] Liczba kroków: 1 Odczyt 0: x=3.0, y=-1.5 ... Odczyt 9: x=3.0, y=-1.5

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.

🧱 Architektura po Lekcji 01

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

🔗 Jak to łączy się z następną lekcją?

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.

🧠 Podsumowanie Lekcji 01

Po tej lekcji mamy:

🔜 Co w Lekcji 02?

👉 Lekcja 02 — Ruch w czasie

Dodamy:

🧠 Złota myśl:

Ruch to zmiana stanu.
Ale najpierw musi istnieć coś, co można zmienić.

🐍 Nowe rozkazy Pythona w tej lekcji

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

💡 Krotka vs lista

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

💡 List comprehension

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]

📋 Ściągawka — Lekcja 01

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

✅ Checklista — Obiekt i stan

Sprawdź, czy naprawdę rozumiesz:

🎯 Zadania z charakterem

🟢 Zadanie 1 — Co naprawdę jest stanem?

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)

🟢 Zadanie 2 — Ruch bez prędkości

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)

🟡 Zadanie 3 — Dane vs operacje

Masz:

Zró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)

🟡 Zadanie 4 — Napisz własne funkcje ruchu

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.


🔴 Zadanie 5 — Tożsamość obiektu

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)

🏆 Zadanie mistrzowskie — Object bez atrybutów

Wyobraź sobie klasę Object bez x i y:

class ObjectBezStanu:
    def move_right(self, step):
        pass  # co tu miałoby się zmienić?

Następnie udowodnij swoją odpowiedź kodem.

✅ Odpowiedzi — Checklista

  1. TakObject ma x i y, tworzone w __init__ jako self.x = x, self.y = y.
  2. Takx i y to aktualny stan — gdzie obiekt jest teraz. Nie historia, nie przyszłość.
  3. Takhistory to lista krotek [(x, y), ...]. Zaczyna od [(x_start, y_start)], rośnie po każdym save().
  4. Taksave() wykonuje self.history.append((self.x, self.y)) — dokłada aktualną pozycję na koniec listy.
  5. Takmove_right() modyfikuje self.x istniejącego obiektu. obj = Object(...) tworzy raz, potem tylko modyfikujemy.
  6. Tak — brak vx, vy. Prędkość pojawi się w L02.
  7. TakObject nie zna World. Nie ma atrybutu world ani time. To celowe — separacja odpowiedzialności.
  8. scatter rysuje niezależne punkty (obserwacje stanu). plot łączy punkty linią (przebieg w czasie). W L01 nie mamy jeszcze procesu w czasie — tylko punkty.
  9. def move_left(obj, step): obj.x -= step; obj.save() — minus zamiast plus.
  10. Taklista[-1] w Pythonie to ostatni element. lista[-2] to przedostatni. Działa dla każdej listy.
  11. xs = [p[0] for p in obj.history] — list comprehension wyciąga pierwszą wartość każdej krotki.
  12. Tak — każda instancja Object ma własne x, y, history. a.x = 5 nie zmienia b.x.

🎯 Odpowiedzi — Zadania z charakterem

🟢 Zadanie 1 — Co naprawdę jest stanem?

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_bFalse).

x i y należą do obiektu — są jego atrybutami, nie globalnymi zmiennymi.

move_right() to operacja, nie stan — stan to tylko dane (x, y).

🟢 Zadanie 2 — Ruch bez prędkości

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.

🟡 Zadanie 3 — Dane vs operacje

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

🟡 Zadanie 4 — Napisz własne funkcje ruchu

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

🔴 Zadanie 5 — Tożsamość obiektu

Po 10 ruchach id(obj) == original_idTrue — 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.

🏆 Zadanie mistrzowskie — Object bez atrybutów

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.

🧠 Zasada Profesora

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.


🔥 Złota Myśl

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