(czyli: zanim coś się ruszy, musi istnieć) 🌍
Autor: Asprocool | Kontakt: Asprocool@int.pl
Jesteś na początku. To lekcja zerowa — fundament pod wszystko co nastąpi.
To lekcja zerowa. Jedyna zmiana: świat umie liczyć czas.
| Co dodajemy | Typ | Po co |
|---|---|---|
class World |
klasa | pierwszy byt w symulacji |
time |
atrybut | zegar świata |
dt |
atrybut | rozmiar jednego kroku |
history |
atrybut (lista) | pamięć przeszłości |
step() |
metoda | jedna tura — czas idzie do przodu |
Ta lekcja jest pierwsza — nie potrzebujesz nic z poprzednich.
Potrzebujesz tylko:
matplotlib (pip install matplotlib)Jeśli to Twój pierwszy kontakt z programowaniem — spokojnie. Każda nowa komenda będzie wyjaśniona od podstaw.
Zrób to raz przed pierwszą lekcją. Potem już tylko otwierasz Jupyter i pracujesz.
⚠️ WAŻNE — Windows: Na pierwszym ekranie instalatora zaznacz ✅ "Add Python to PATH"
Bez tego nic nie będzie działać z terminala.
Sprawdź czy działa — otwórz terminal i wpisz:
python --version
Powinieneś zobaczyć: Python 3.x.x
Jak otworzyć terminal?
Win + R, wpisz cmd, Enter — albo wyszukaj „Wiersz polecenia" w menu StartCommand + Spacja, wpisz Terminal, EnterCtrl + Alt + TW terminalu wpisz kolejno:
pip install jupyter
pip install jupyterlab
pip install matplotlib
pip install numpy
Albo wszystko naraz:
pip install jupyter jupyterlab matplotlib numpy
Instalacja może potrwać kilka minut. Zobaczysz dużo tekstu — to normalne.
❓ Jeśli pip nie działa: Spróbuj pip3 zamiast pip.
Na Windows może też pomóc: python -m pip install jupyterlab matplotlib numpy
Sprawdź czy działa:
jupyter --version
Powinieneś zobaczyć kilka linii z wersjami.
cd C:\Users\TwojeImie\Desktop\kurs-ai # Windows
cd ~/Desktop/kurs-ai # Mac / Linux
mkdir kurs-ai
cd kurs-ai
jupyter lab
http://localhost:8888/lab
⚠️ Nie zamykaj terminala podczas pracy — to serwer Jupyter.
Przeglądarka to tylko interfejs graficzny.
Jeśli pobrałeś plik .ipynb:
Jeśli tworzysz nowy:
File → New → NotebookPython 3| Akcja | Skrót |
|---|---|
| Uruchom komórkę i przejdź do następnej | Shift + Enter |
| Uruchom komórkę i zostań | Ctrl + Enter |
| Nowa komórka poniżej | B (poza trybem edycji) |
| Nowa komórka powyżej | A (poza trybem edycji) |
| Usuń komórkę | DD (dwa razy D, poza trybem edycji) |
| Zmień na Markdown | M (poza trybem edycji) |
| Zmień na kod | Y (poza trybem edycji) |
| Uruchom wszystkie komórki | Run → Run All Cells |
| Zatrzymaj | ■ (przycisk Stop na pasku) |
💡 Tryb edycji (kursor miga w komórce) ↔ tryb poleceń (komórka zaznaczona, bez kursora)
Wyjdź z edycji: Escape. Wejdź w edycję: Enter lub kliknij dwukrotnie.
| Problem | Co zrobić |
|---|---|
pip nie jest rozpoznany |
Spróbuj pip3 lub python -m pip install ... |
jupyter nie jest rozpoznany |
Zamknij terminal, otwórz nowy i spróbuj ponownie |
| Przeglądarka nie otwiera się | Wpisz ręcznie http://localhost:8888/lab |
| Port zajęty (błąd 8888) | jupyter lab --port=8889 |
| Wykres nie pojawia się | Dodaj %matplotlib inline jako pierwszą linię w komórce z kodem |
| Kernel się zawiesił | Kernel → Restart Kernel |
| Wszystko się posypało | Zamknij Jupyter, zamknij terminal, zacznij od Kroku 3 |
# ✅ TEST INSTALACJI — uruchom tę komórkę (Shift+Enter)
# Jeśli nie ma błędów — wszystko działa i możesz zacząć!
import matplotlib.pyplot as plt
import numpy as np
print("✅ Python — OK")
print("✅ numpy — wersja: ", np.__version__)
print("✅ matplotlib — OK")
print()
print("🚀 Środowisko gotowe! Możesz zacząć Lekcję 00.")
Po tej lekcji:
ALE:
I to wystarczy, żeby zacząć prawdziwą symulację.
⚠️ Celowo nudne. Jeśli wydaje Ci się „za proste" — dokładnie o to chodzi 😄
W XVII wieku Isaac Newton twierdził, że czas płynie absolutnie — jednakowo wszędzie we wszechświecie, niezależnie od obserwatora. Był jak wielka rzeka płynąca sama z siebie.
Gottfried Wilhelm Leibniz się nie zgadzał. Twierdził, że czas to tylko relacja między zdarzeniami — nie istnieje sam w sobie, tylko jako porządek następujących po sobie chwil.
Przez 200 lat wygrywał Newton. Dopiero Einstein w 1905 roku pokazał, że Leibniz miał rację — czas jest względny, zależy od obserwatora i prędkości.
W naszej symulacji czas jest leibnizowski — to po prostu licznik kroków. World nie wie ile „prawdziwego czasu" minęło. Wie tylko że wykonał N kroków. Każde step() to jedno zdarzenie. Czas to relacja między zdarzeniami.
🧠 Paradoks: najprostszy model czasu w kodzie jest bliższy prawdzie niż intuicja Newtona.
Jeśli świat nie zrobi kroku — nic się nie wydarzy.
Nawet najlepsza fizyka.
Na razie świat nie ma przestrzeni, obiektów ani zjawisk. Umie tylko jedno: ⏱️ wiedzieć, która jest chwila symulacji.
Wyobraź sobie zegarek ⌚ który:
Każde „tyk" = jeden krok symulacji.
| Rozkaz | Co robi | Przykład |
|---|---|---|
class NazwaKlasy: |
tworzy nowy typ danych | class World: |
def __init__(self, ...) |
konstruktor — uruchamia się przy tworzeniu obiektu | def __init__(self, dt): |
self.x = ... |
zapisuje wartość w obiekcie | self.time = 0.0 |
def metoda(self): |
funkcja należąca do obiektu | def step(self): |
obiekt = Klasa(...) |
tworzy konkretny egzemplarz | world = World(dt=0.1) |
for i in range(n): |
pętla wykonująca się n razy | for i in range(10): |
lista = [] |
pusta lista | self.history = [] |
lista.append(x) |
dodaje element do listy | self.history.append(0.0) |
print(f"...") |
wyświetla tekst z wartościami | print(f"czas = {t:.2f}") |
classclass to przepis na obiekt. Nie sam obiekt — tylko instrukcja jak go stworzyć.
class Samochod:
...
auto = Samochod() # ← dopiero TERAZ powstaje konkretny egzemplarz
class w symulacji?Bo świat ma stan (wie ile czasu minęło), zachowanie (potrafi wykonać krok) i żyje w czasie (zmienia się krok po kroku). Klasy są idealne do opisywania „czegoś, co istnieje i ma pamięć".
class World:
def __init__(self, dt):
self.time = 0.0 # aktualny czas świata
self.dt = dt # krok czasu
def step(self):
# Wykonaj jeden krok symulacji
self.time += self.dt
class World:
Tworzymy nowy typ danych o nazwie World. Po tej deklaracji Python wie że istnieje coś takiego jak „świat".
def __init__(self, dt):
Konstruktor — specjalna metoda która uruchamia się automatycznie gdy piszesz World(dt=0.1). Słowo self znaczy: „ten konkretny obiekt".
self.time = 0.0 # aktualny czas świata
Każdy świat dostaje swój własny zegar. Zaczynamy od zera. self.time — to atrybut: własna zmienna tego konkretnego obiektu.
self.dt = dt # krok czasu
dt = delta time = ile czasu mija w jednym kroku. Zapisujemy to w obiekcie żeby pamiętał swoją rozdzielczość.
def step(self):
self.time += self.dt
Metoda — funkcja należąca do obiektu. step() robi dokładnie jedną rzecz: przesuwa czas o dt.
| Błąd | Co się dzieje | Jak naprawić |
|---|---|---|
Zapominasz wywołać world.step() |
Czas stoi w miejscu | Dodaj world.step() do pętli |
Myliš dt z time |
Nieprawidłowe obliczenia | dt = stały krok; time = aktualny czas |
Piszesz World.step() bez instancji |
TypeError |
Najpierw world = World(dt=0.1) |
Zapominasz self. w metodzie |
NameError |
Zawsze self.time, nie samo time |
step()Metoda to funkcja należąca do obiektu — coś co obiekt potrafi zrobić.
def step(self):
self.time += self.dt # przesuń czas o jeden krok
Co robi step()? Przesuwa czas o dt. Nic więcej.
Nie porusza, nie liczy, nie symuluje. Po prostu: czas idzie do przodu.
W kolejnych lekcjach step() będzie robić więcej — ale zawsze to właśnie w nim będzie bił puls symulacji. Każde wywołanie step() = jedna tura.
Czas rośnie o 0.1 po każdym kroku. Świat żyje 🎉
Jeśli nie widzisz tego wyniku — sprawdź czy wywołałeś world.step() w pętli.
world = World(dt=0.1)
for i in range(10):
world.step()
print(f"Krok {i:02d} | czas świata = {world.time:.2f}")
world = World(dt=0.1)
Tworzymy konkretny świat z krokiem dt=0.1. Od tej chwili world to nasz obiekt — ma swój time, dt i zaraz history.
for i in range(10):
Pętla — wykona się 10 razy. i przyjmuje wartości 0, 1, 2, ..., 9. range(10) generuje liczby od 0 do 9 (nie do 10!).
world.step()
Jeden krok symulacji — czas rośnie o dt. Bez tej linii czas by stał w miejscu.
print(f"Krok {i:02d} | czas świata = {world.time:.2f}")
f"..." — f-string: wstawiamy zmienne w {}{i:02d} — liczba całkowita, minimum 2 cyfry, wypełniona zerami: 00, 01...{world.time:.2f} — liczba zmiennoprzecinkowa, 2 miejsca po przecinku🧠 Ważna obserwacja: Czas w symulacji to umowa, nie prawda absolutna. Świat wie tylko że wykonał N kroków — nie wie czym jest „sekunda".
[]Lista to pojemnik na wiele wartości w kolejności.
historia = [] # pusty pojemnik
historia.append(0.0) # dodajemy → [0.0]
historia.append(0.1) # dodajemy → [0.0, 0.1]
historia.append(0.2) # dodajemy → [0.0, 0.1, 0.2]
Analogie:
W naszym świecie:
self.history = [0.0] # zaczynamy z czasem 0.0
self.history.append(self.time) # po każdym step() → dodajemy aktualny czas
Dzięki temu świat pamięta całą swoją przeszłość — będzie to kluczowe w lekcjach o analizie ruchu.
matplotlib?matplotlib to biblioteka do rysowania wykresów w Pythonie. Używają jej naukowcy, inżynierowie i twórcy gier do analizy danych.
import matplotlib.pyplot as plt # importujemy moduł do rysowania
plt.figure() # nowe okno wykresu
plt.plot(x, y, 'o') # rysuj punkty (marker 'o' = kółko)
plt.xlabel("Krok symulacji") # opis osi X
plt.ylabel("Czas świata") # opis osi Y
plt.title("Tytuł wykresu") # tytuł
plt.grid(True) # siatka w tle
plt.show() # pokaż wykres
Nie robimy grafiki gry — robimy narzędzie diagnostyczne 🔬
Wykres z 11 punktami (o) ułożonymi wzdłuż linii prostej. Oś X: numery kroków (0–10). Oś Y: czas (0.0–2.5). Punkty rozłożone równomiernie — bo dt jest stałe.
world.history to lista w pamięci Pythona — istnieje tylko dopóki Jupyter jest uruchomiony. Zamkniesz notebook → dane znikają.
CSV (Comma-Separated Values) to plik tekstowy który:
world.history i CSV zawierają to samo?Tak — dokładnie to samo.
world.history |
plik CSV | |
|---|---|---|
| Gdzie jest? | w pamięci Pythona | na dysku |
| Jak zobaczyć? | print(world.history) |
otwórz w Excelu |
| Znika po zamknięciu? | ❌ tak | ✅ nie |
| Można wysłać? | ❌ nie bezpośrednio | ✅ tak |
import matplotlib.pyplot as plt
import csv
# ======================
# Definicja klasy World z historią
# ======================
class World:
def __init__(self, dt):
self.time = 0.0
self.dt = dt
self.history = [0.0] # zaczynamy z czasem 0.0
def step(self):
self.time += self.dt
self.history.append(self.time)
# ======================
# Funkcja rysująca wykres czasu
# ======================
def draw_time(world):
steps = list(range(len(world.history)))
plt.figure()
plt.plot(steps, world.history, 'o')
plt.xlabel("Krok symulacji")
plt.ylabel("Czas świata")
plt.title("Czas w symulacji (krok po kroku)")
plt.grid(True)
plt.show()
# ======================
# Symulacja — 10 kroków
# ======================
world = World(dt=0.25)
for i in range(10):
world.step()
draw_time(world)
# Sprawdź w pamięci — to samo co będzie w pliku:
print("world.history: ", world.history)
# ======================
# 💾 Export do CSV — zapisujemy historię na dysk
# ======================
with open('lekcja_00.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['krok', 'czas']) # nagłówek
for i, t in enumerate(world.history):
writer.writerow([i, round(t, 6)]) # dane
print(f"\n✅ Zapisano {len(world.history)} wierszy do lekcja_00.csv")
print(" Plik powstał w tym samym folderze co notebook.")
print(" Otwórz w Excelu lub w jupyter.")
print()
print("Zawartość pliku powinna wyglądać tak: ")
print(" krok,czas")
for i, t in enumerate(world.history):
print(f" {i},{round(t, 6)}")
self.history = [0.0]
Lista startuje z jedną wartością — czasem 0.0 na początku symulacji. [0.0] to skrót od [] + append(0.0).
self.history.append(self.time)
Po każdym step() dokładamy aktualny czas do listy. Lista rośnie z każdym krokiem.
steps = list(range(len(world.history)))
len(world.history) → liczba elementów w liście (np. 11)range(11) → generator liczb 0, 1, 2, ..., 10list(...) → zamienia generator na listę [0, 1, 2, ..., 10]To są numery kroków — oś X wykresu.
plt.plot(steps, world.history, 'o')
Rysuje punkty: X = numer kroku, Y = czas. 'o' = kółka zamiast linii.
🧠 Co pokazuje ten wykres?
- każda kropka = jedno tyknięcie świata
- czas rośnie skokowo, nie płynnie
- odstępy między kropkami = wartość
dtSymulacja nie płynie. Symulacja przeskakuje.
Mniejszedt→ gęstsze kropki → dokładniejsza symulacja.
💡 Jak uruchamiać eksperymenty?
Każdy eksperyment to zmiana w kodzie. Skopiuj odpowiedni fragment, wklej do nowej komórki w Jupyter i uruchom (Shift+Enter). Przed każdym eksperymentem upewnij się że uruchomiłeś wcześniej komórkę z definicją klasy World i wizualizacją.
⚠️ Uruchom najpierw komórkę z wizualizacją czasu (tę z draw_time). Dopiero potem uruchom ten eksperyment — world musi już istnieć.
Co robisz: W nowej komórce wpisz i uruchom:
print(world.history)
Co zobaczysz:
Szczegółowe wyjaśnienie: Lista ma 11 elementów: 0.0 na starcie + 10 wartości po każdym step(). Pierwsza wartość 0.0 pochodzi z inicjalizacji self.history = [0.0] w __init__. Ostatnia wartość 2.5 = 10 kroków × dt=0.25. To jest dokładnie to co było rysowane na wykresie — każda kropka = jedna liczba z tej listy.
Dlaczego to ważne: W kolejnych lekcjach history będzie przechowywać pozycje, prędkości, błędy trafienia. Umiejętność podejrzenia historii = umiejętność debugowania symulacji. Pamiętaj: możesz też sprawdzić konkretny element: world.history[0], world.history[-1].
Co robisz: Przepisz całą klasę World z zamienioną kolejnością w step(), utwórz nowy świat i sprawdź jego historię:
class WorldV2:
def __init__(self, dt):
self.time = 0.0
self.dt = dt
self.history = [0.0]
def step(self):
self.history.append(self.time) # ← NAJPIERW zapisz (stara wartość!)
self.time += self.dt # ← POTEM zwiększ
world_v2 = WorldV2(dt=0.25)
for _ in range(4):
world_v2.step()
print("Oryginał:", [0.0, 0.25, 0.5, 0.75, 1.0]) # dla porównania
print("V2: ", world_v2.history)
Co zobaczysz:
Szczegółowe wyjaśnienie:
time += dt (czas rośnie do 0.25), potem append(0.25) → lista = [0.0, 0.25, ...]append(0.0) (zapisujemy starą wartość!), potem time += dt → lista = [0.0, 0.0, 0.25, ...]0.0, 0.0 — bo zapisujemy czas przed jego zwiększeniem1.0) — bo po ostatnim append czas dopiero rośnie, ale już nie zapisujemyDlaczego to ważne: Kolejność operacji w każdym step() będzie krytyczna w fizyce. W L05+ gdy będziemy liczyć prędkości i pozycje, zła kolejność = błędna trajektoria. To tzw. off-by-one error — jeden z najczęstszych błędów w symulacjach numerycznych.
Co robisz: Przepisz klasę World z dodatkową listą kroków:
class WorldV3:
def __init__(self, dt):
self.time = 0.0
self.dt = dt
self.history = [0.0]
self.steps = [0] # ← NOWA lista: numery kroków
def step(self):
self.time += self.dt
self.history.append(self.time)
self.steps.append(len(self.steps)) # ← numer = długość przed append
world_v3 = WorldV3(dt=0.25)
for _ in range(4):
world_v3.step()
print("history:", world_v3.history)
print("steps: ", world_v3.steps)
print("Liczba kroków:", world_v3.steps[-1])
Co zobaczysz:
Szczegółowe wyjaśnienie: len(self.steps) przed append zwraca aktualną długość listy — to jednocześnie numer nowego elementu.
len([0]) = 1 → append(1) → [0, 1]len([0,1]) = 2 → append(2) → [0, 1, 2]Obie listy rosną synchronicznie — zawsze mają tę samą długość.
Dlaczego to ważne: W L01+ World będzie zarządzać listą obiektów, każdy z własną historią. Wzorzec „wiele list rosnących synchronicznie" pojawi się wielokrotnie.
dt (OBOWIĄZKOWY)⚠️ Upewnij się że World jest zdefiniowany (uruchom komórkę z wizualizacją). Ten eksperyment tworzy nowe obiekty world_a i world_b — nie nadpisuje world.
Co robisz: Wklej i uruchom w nowej komórce:
import matplotlib.pyplot as plt
world_a = World(dt=0.05) # małe kroki — dokładna symulacja
world_b = World(dt=0.50) # duże kroki — zgrubna symulacja
for _ in range(20):
world_a.step()
world_b.step()
steps_a = list(range(len(world_a.history)))
steps_b = list(range(len(world_b.history)))
plt.figure(figsize=(10, 4))
plt.plot(steps_a, world_a.history, 'o-', label=f'dt=0.05 (czas końcowy: {world_a.time:.2f}s)')
plt.plot(steps_b, world_b.history, 's-', label=f'dt=0.50 (czas końcowy: {world_b.time:.2f}s)')
plt.xlabel("Krok symulacji")
plt.ylabel("Czas świata")
plt.title("Wpływ dt na rozdzielczość czasu")
plt.legend()
plt.grid(True)
plt.show()
print(f"Oba wykonały 20 kroków:")
print(f" dt=0.05 → czas końcowy: {world_a.time:.2f}s (20 × 0.05)")
print(f" dt=0.50 → czas końcowy: {world_b.time:.2f}s (20 × 0.50)")
Co zobaczysz:
Wykres: linia niebieska gęsta (21 punktów blisko siebie), linia pomarańczowa rzadka (21 punktów daleko od siebie).
Szczegółowe wyjaśnienie:
world_a: krok = 0.05s → po 20 krokach = 1 sekunda symulacjiworld_b: krok = 0.50s → po 20 krokach = 10 sekund symulacjidt → gęstsze próbkowanie → dokładniejsza trajektoria w przyszłościdt → rzadsze próbkowanie → szybsze obliczenia, ale więcej błędówDlaczego to ważne: Wybór dt to jeden z kluczowych parametrów każdej symulacji fizycznej. Zbyt duże dt = symulacja „eksploduje" (błędy numeryczne narastają). Zbyt małe dt = symulacja trwa wieki. W naszej serii używamy dt=0.05 jako kompromis — dobre wyniki, rozsądna szybkość.
| Element | Typ | Atrybuty | Metody | Co robi |
|---|---|---|---|---|
World |
klasa | dt, time, history |
step() |
zarządza czasem symulacji |
Jeszcze nie mamy — pojawi się stopniowo:
| Element | Kiedy |
|---|---|
class Object — pozycja x, y, prędkość vx, vy |
L01 |
funkcja update(obj, world) — jeden krok fizyki |
L02 |
class Influence, Gravity, Drag — wpływy świata |
L06 |
class Cannon, Projectile, fire() — działo i strzał |
L11 |
class Replay — zapis i odtwarzanie symulacji |
L18 |
class Target — ruchomy cel |
L19 |
class AdaptiveShooter — działo z feedbackiem |
L20 |
class GameRules, GameLoop — pełna gra |
L23–L24 |
World zarządza czasem. W L01 dodamy Object który zarządza stanem (pozycja x, y, prędkość vx, vy). Oba będą działać razem: świat wyznacza rytm, obiekt zmienia stan w każdym kroku.
Po tej lekcji mamy:
World — pierwszy byt w symulacjitime — zegar światadt — krok czasu (rozdzielczość)history — pamięć przeszłościstep() — jedna tura symulacji👉 Lekcja 01 — Object i stan
Dodamy:
class Object z pozycją x, yvx, vy🧠 Złota myśl:
Ruch bez struktury to chaos.
Struktura bez ruchu to potencjał.Lekcja 00 to moment, w którym budujesz miejsce, w którym coś będzie mogło się dziać. 🚀
for, print i f-stringiforPętla for powtarza blok kodu określoną liczbę razy.
for i in range(5):
print(i)
# Wypisze: 0 1 2 3 4
range(5) — generuje liczby od 0 do 4 (nie do 5!).
range(1, 6) — generuje liczby od 1 do 5.
range(0, 10, 2) — co drugi: 0, 2, 4, 6, 8.
Zastosowanie w symulacji:
for krok in range(100):
world.step() # wykonaj 100 kroków świata
Przed cudzysłowem stawiasz f, zmienne wstawiasz w {}:
imie = "Symulacja"
krok = 5
czas = 0.123456
print(f"To jest {imie}, krok nr {krok}, czas = {czas:.3f}.")
# → To jest Symulacja, krok nr 5, czas = 0.123.
| Zapis | Co robi | Wejście | Wyjście |
|---|---|---|---|
{x:.2f} |
2 miejsca po przecinku | 3.14159 |
3.14 |
{x:.0f} |
zaokrąglij do całości | 3.7 |
4 |
{x:03d} |
liczba z zerami (3 cyfry) | 5 |
005 |
{x:8.3f} |
szerokość 8, 3 miejsca | 1.5 |
···1.500 |
{x:<10} |
wyrównaj do lewej | abc |
abc······· |
{x:>10} |
wyrównaj do prawej | abc |
·······abc |
{x:^10} |
wyśrodkuj | abc |
···abc···· |
{x:*^10} |
wyśrodkuj z wypełniaczem | abc |
***abc**** |
🧱 Złota zasada: Najpierw wyrównanie (<, >, ^), potem szerokość, na końcu precyzja (.2f).
Przykład: {self.time:^10.2f} → wyśrodkuj na 10 polach, 2 miejsca po przecinku.
import matplotlib.pyplot as plt
class World:
def __init__(self, dt):
self.time = 0.0
self.dt = dt
# Zaczynamy z czasem 0.0 w historii
self.history = [0.0]
def step(self):
self.time += self.dt
self.history.append(self.time)
world_time = 0.0
dt = 0.25
print(f"{'KROK': <5} | {'CZAS ŚWIATA': <12} | {'STATUS'}")
print("-" * 35)
for i in range(1, 6):
world_time += dt
# i:02d -> liczba 2-cyfrowa z zerem (01, 02...)
# world_time:.3f -> czas z 3 miejscami po przecinku
print(f"#{i:02d} | {world_time: <12.3f} | ✅ OK")
| Co | Jak | Uwaga |
|---|---|---|
| Utwórz świat | world = World(dt=0.1) |
dt = rozmiar kroku |
| Jeden krok | world.step() |
zwiększa time o dt |
| 10 kroków | for _ in range(10): world.step() |
_ gdy nie potrzebujesz licznika |
| Aktualny czas | world.time |
rośnie po każdym step() |
| Cała historia | world.history |
lista wszystkich wartości time |
| Dodaj do listy | lista.append(wartość) |
na końcu listy |
| Liczba elementów | len(lista) |
np. len(world.history) |
| Rysuj wykres | plt.plot(x, y, 'o') |
'o' = punkty, '-' = linia |
| Pokaż wykres | plt.show() |
zawsze na końcu |
| Export CSV | writer.writerow([i, t]) |
krok po kroku w pętli |
Sprawdź, czy naprawdę rozumiesz:
World posiada atrybut time?time jest inicjalizowane wartością 0.0?dt jest przekazywane w konstruktorze i zapisywane w obiekcie?step() wykonuje self.time += self.dt?time zmienia się wyłącznie w step()?history to lista kolejnych wartości time?World nie ma żadnych atrybutów ruchu (pozycja, prędkość)?World nie ma listy obiektów?time += dt?World to zegar — nie silnik fizyczny?dt a time własnymi słowami?dt=0.05 od dt=1.0 w praktyce?world.step() 100 razy używając pętli for?Pytania do przemyślenia:
Zrób to w Jupyter: Uruchom poniższy kod i odpowiedz — czy dwa światy są identyczne?
world_x = World(dt=0.1)
world_y = World(dt=0.1)
for _ in range(5):
world_x.step()
world_y.step()
print("world_x.time:", world_x.time)
print("world_y.time:", world_y.time)
print("Czy identyczne?", world_x.time == world_y.time)
Co to mówi o naturze tego „świata"?
dtPytania:
dt od time? Podaj analogię z życia codziennego.dt należy do świata, a nie do przyszłego obiektu?Zrób to w Jupyter: Uzupełnij i uruchom:
# Utwórz świat z dt=0, wykonaj krok i sprawdź czas
world_zero = World(dt=0)
world_zero.step()
print("czas po kroku z dt=0:", world_zero.time)
# Co widzisz? Dlaczego?
step()step() zmienia coś poza time?step() dwa razy w jednej turze zamiast raz?Zrób to w Jupyter: Dodaj licznik wywołań do World. Przepisz klasę z tym rozszerzeniem i sprawdź czy działa:
class WorldZLicznikiem:
def __init__(self, dt):
self.time = 0.0
self.dt = dt
self.history = [0.0]
self.step_count = 0 # ← NOWOŚĆ: licznik wywołań
def step(self):
self.time += self.dt
self.history.append(self.time)
self.step_count += 1 # ← zliczamy każde wywołanie
w = WorldZLicznikiem(dt=0.1)
for _ in range(5):
w.step()
print("Czas:", w.time)
print("Liczba wywołań step():", w.step_count)
print("Czy są równe?", round(w.time, 5) == round(w.dt * w.step_count, 5))
⚠️ Zainicjuj history jako [(0, 0.0)] — nie jako [0.0], bo inaczej pierwszy element będzie innego typu niż reszta.
Zmodyfikuj World tak, żeby history zapisywało krotki (numer_kroku, czas):
class WorldZKrotkami:
def __init__(self, dt):
self.time = 0.0
self.dt = dt
self.history = [(0, 0.0)] # ← krotka od razu
def step(self):
self.time += self.dt
krok = len(self.history)
self.history.append((krok, self.time))
w = WorldZKrotkami(dt=0.25)
for _ in range(4):
w.step()
print("Historia:", w.history)
# Wyciągnij same czasy i sprawdź różnice:
czasy = [t for _, t in w.history]
diffs = [czasy[i+1] - czasy[i] for i in range(len(czasy)-1)]
print("Różnice:", diffs) # powinny być równe dt
history? Usuń ją z klasy i sprawdź.W tej lekcji świat nie ma: prędkości, przyspieszenia, sił, obiektów.
World2 z dwoma niezależnymi zegarami: time_fast (dt=0.1) i time_slow (dt=1.0).step(). Czy to poprawne? Kiedy może być przydatne?class World2:
def __init__(self):
# Uzupełnij: dwa atrybuty time_fast i time_slow
pass
def step(self):
# Uzupełnij: oba zegary tykają w tej samej metodzie
pass
w2 = World2()
for _ in range(5):
w2.step()
# Wypisz oba czasy po 5 krokach — co obserwujesz?
world1 = World(dt=0.1)
world2 = World(dt=0.2)
for _ in range(100):
world1.step()
world2.step()
print(world1.time)
print(world2.time)
Część 1: Czy wyniki zależą od jakiejkolwiek fizyki?
Część 2: Czy istnieje w modelu jakakolwiek zmienna przestrzenna?
Część 3: Udowodnij że World to zegar — napisz klasę Zegar która robi dokładnie to samo bez użycia słowa World, time, dt, step, history. Użyj własnych nazw. Czy zachowanie jest identyczne?
Jeśli potrafisz odpowiedzieć — rozumiesz Lekcję 00 jako fundament całego kursu.
World posiada atrybut time, tworzony w __init__ przez self.time = 0.0.time = 0.0 bo każda symulacja zaczyna od chwili „zero" — jak stoper przed startem wyścigu.dt jest parametrem konstruktora i zapisywany jako self.dt = dt. Każdy obiekt World może mieć inne dt — jeden świat może tykać co 0.05s, inny co 1.0s.step() wykonuje dokładnie self.time += self.dt. To jedyna operacja arytmetyczna w całej lekcji.time jest modyfikowane wyłącznie wewnątrz step(). W żadnym innym miejscu nie piszemy self.time = .... To ważna zasada enkapsulacji.history to lista []; self.history.append(self.time) dodaje wartość na koniec listy po każdym wywołaniu step().x, y, vx, vy, ax, ay. Świat L00 nie wie nic o przestrzeni — tylko o czasie.objects = []. Świat L00 nie zarządza jeszcze żadnymi bytami w przestrzeni.time += dt i zapis do history. Wszystko inne jest statyczne lub nie istnieje.World to deterministyczny licznik. Przy tych samych parametrach i tej samej liczbie kroków zawsze daje identyczny wynik. Zero losowości, zero fizyki.dt = stały krok (np. 0.1) — nie zmienia się przez całą symulację. time = aktualny czas — rośnie o dt po każdym step(). Analogia: dt = długość jednego kroku pieszego (np. 70cm), time = całkowita przebyta droga.dt=0.05 → czas końcowy 1.0s (gęste punkty, dokładna symulacja). dt=1.0 → czas końcowy 20.0s (rzadkie punkty, zgrubna symulacja). Oba wykonały 20 kroków — ale ich „światy" żyją w różnym tempie.for _ in range(100): world.step(). Podkreślenie _ zamiast zmiennej i jest konwencją Pythona gdy nie używamy licznika.Tak — świat może istnieć bez ruchu.
Brak dynamiki nie oznacza braku struktury. Klasa World z atrybutami time, dt i metodą step() to w pełni działający system — ma stan, ma zachowanie, żyje w czasie. Po prostu nic jeszcze nie „robi" poza liczeniem czasu.
Zaczynamy od czasu dlatego, że w symulacji fizycznej każda zmiana jest opisana w czasie: pozycja zmienia się co dt sekund, prędkość to zmiana pozycji w czasie, przyspieszenie to zmiana prędkości w czasie. Bez kontrolowanego kroku czasu nie ma sensu mówić o żadnym ruchu.
Analogia: zanim zbudujesz dom, musisz mieć działkę. World to działka — pusta, ale gotowa na budowę.
dtdt to rozmiar jednego kroku — stały przez całą symulację, nie zmienia się. time to aktualny czas — rośnie o dt po każdym wywołaniu step().
Analogia z życia: dt to długość jednego kroku pieszego (np. 70cm — każdy krok jest taki sam). time to całkowita przebyta droga (po 10 krokach = 7m, po 100 krokach = 70m).
Jeśli ustawisz dt = 0:
world = World(dt=0)
world.step()
print(world.time) # → 0.0 — czas nigdy nie rośnie!
Symulacja się kręci, ale czas stoi w miejscu. Martwa symulacja.
dt należy do świata bo to świat odpowiada za rytm symulacji. Obiekt (który dodamy w L01) tylko reaguje na ten rytm — nie wyznacza go. Gdyby każdy obiekt miał własne dt, mogłyby żyć w różnych tempach i nie dałoby się ich zsynchronizować.
step()W L00 step() zmienia wyłącznie time (i history). W L05+ będzie też aktualizować pozycje i prędkości — ale zawsze właśnie w step().
Dwa wywołania step() w jednej turze → czas przeskakuje o 2*dt → fizyka w przyszłych lekcjach się rozjeżdża.
Pełna klasa z licznikiem i weryfikacją:
class WorldZLicznikiem:
def __init__(self, dt):
self.time = 0.0
self.dt = dt
self.history = [0.0]
self.step_count = 0 # ← licznik wywołań
def step(self):
self.time += self.dt
self.history.append(self.time)
self.step_count += 1 # ← zliczamy każde wywołanie
w = WorldZLicznikiem(dt=0.1)
for _ in range(5):
w.step()
print("Czas:", w.time) # → 0.5
print("Wywołań step():", w.step_count) # → 5
print("Czas == dt × kroki:", round(w.time, 5) == round(w.dt * w.step_count, 5)) # → True
Co by się stało przy dwóch step() w jednej turze?
w2 = WorldZLicznikiem(dt=0.1)
w2.step()
w2.step() # ← BŁĄD: dwa razy w jednej "turze"
print(w2.time) # → 0.2 zamiast 0.1 — czas przeskoczył!
class World:
def __init__(self, dt):
self.time = 0.0
self.dt = dt
self.history = [(0, 0.0)] # ← zaczynamy od krotki, nie od float!
def step(self):
self.time += self.dt
krok = len(self.history) # numer kroku = długość listy przed append
self.history.append((krok, self.time))
Symulacja działa bez history — step() nadal zwiększa time. history to narzędzie analityczne, nie dynamiczne.
Wykres prędkości wzrostu (powinien być płaski — stały dt):
# history przechowuje krotki (krok, czas) — wyciągamy sam czas
czasy = [t for _, t in world.history]
diffs = [czasy[i+1] - czasy[i] for i in range(len(czasy)-1)]
print(diffs) # każda wartość powinna być równa dt
# np. [0.25, 0.25, 0.25, 0.25, ...]
To poprawna struktura — uproszczona, ale spójna. Warstwowe budowanie systemów to klucz inżynierii oprogramowania. Każda warstwa robi jedną rzecz dobrze.
class World2:
def __init__(self):
self.time_fast = 0.0 # dt = 0.1 — czas fizyczny
self.time_slow = 0.0 # dt = 1.0 — czas gameplayowy
def step(self):
self.time_fast += 0.1
self.time_slow += 1.0
Dwa zegary w jednym step() to poprawne i przydatne — np. jeden tyka co 0.05s (fizyka: ruch pocisku), drugi co 1.0s (gameplay: tura gracza). To wzorzec stosowany w grach: physics tick vs game tick.
Wyniki zależą wyłącznie od dt i liczby kroków. Zero fizyki. Nie ma zmiennej przestrzennej.
class Zegar:
def __init__(self, krok):
self.wartosc = 0.0
self.krok = krok
self.historia = [0.0]
def tik(self):
self.wartosc += self.krok
self.historia.append(self.wartosc)
Zegar robi identycznie to samo co World. Różni się tylko nazewnictwem. To dowód że World to zegar — nic więcej, nic mniej. Nazwa klasy ≠ jej zachowanie.
World to deterministyczny licznik kroków. Czas w symulacji to umowa, nie prawda absolutna.
Najpierw definiujesz strukturę świata, dopiero potem pozwalasz mu się poruszać.
Świat to: kontener, organizator, nadrzędna struktura.
Nie: ruch, siła, dynamika.
Jeśli architektura nie istnieje — każda fizyka będzie przypadkiem.
Ruch bez struktury to chaos.
Struktura bez ruchu to potencjał.
📚 Koniec Lekcji 00
Następna lekcja: Lekcja 01 — Object i stan
Autor: Asprocool | Kontakt: Asprocool@int.pl