📓 Lekcja 00 — Świat, który jeszcze nic nie robi

(czyli: zanim coś się ruszy, musi istnieć) 🌍

Autor: Asprocool | Kontakt: Asprocool@int.pl

📍 Gdzie jesteśmy?

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

Jesteś na początku. To lekcja zerowa — fundament pod wszystko co nastąpi.


🔄 Co nowego w tej lekcji

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

⚠️ Zanim zaczniesz

Ta lekcja jest pierwsza — nie potrzebujesz nic z poprzednich.

Potrzebujesz tylko:

Jeśli to Twój pierwszy kontakt z programowaniem — spokojnie. Każda nowa komenda będzie wyjaśniona od podstaw.

🛠️ Instalacja środowiska — krok po kroku

Zrób to raz przed pierwszą lekcją. Potem już tylko otwierasz Jupyter i pracujesz.

💻 Krok 1 — Zainstaluj Pythona

  1. Wejdź na python.org/downloads
  2. Pobierz najnowszą wersję Python 3.x (np. 3.11 lub 3.12)
  3. Uruchom instalator

⚠️ WAŻNE — Windows: Na pierwszym ekranie instalatora zaznacz ✅ "Add Python to PATH"
Bez tego nic nie będzie działać z terminala.

  1. Kliknij „Install Now" i poczekaj

Sprawdź czy działa — otwórz terminal i wpisz:

python --version

Powinieneś zobaczyć: Python 3.x.x

📦 Krok 2 — Zainstaluj biblioteki

Jak otworzyć terminal?

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

🚀 Krok 3 — Uruchom Jupyter Lab

  1. W terminalu przejdź do folderu gdzie chcesz trzymać pliki kursów:
    cd C:\Users\TwojeImie\Desktop\kurs-ai  # Windows
    cd ~/Desktop/kurs-ai  # Mac / Linux
    Jeśli folder nie istnieje — utwórz go najpierw:
    mkdir kurs-ai
    cd kurs-ai
  2. Uruchom Jupyter Lab:
    jupyter lab
  3. Automatycznie otworzy się przeglądarka internetowa z interfejsem:
    http://localhost:8888/lab

⚠️ Nie zamykaj terminala podczas pracy — to serwer Jupyter.
Przeglądarka to tylko interfejs graficzny.

📂 Krok 4 — Otwórz notebook z kursem

Jeśli pobrałeś plik .ipynb:

Jeśli tworzysz nowy:

▶️ Krok 5 — Uruchamianie komórek

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.

❓ Częste problemy

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)
# ✅ 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.")

🎯 Cel lekcji

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 😄

🏛️ Anegdota historyczna

Newton vs Leibniz — co to jest czas?

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.

🧱 Zasada nr 0 (święta)

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.

🧠 Model myślowy

Wyobraź sobie zegarek ⌚ który:

Każde „tyk" = jeden krok symulacji.

🐍 Nowe rozkazy Pythona w tej lekcji

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

🧱 Pojęcie nr 1 — class

(czyli: czym jest klasa i po co ona w ogóle istnieje)

💡 Dla nowicjusza

class to przepis na obiekt. Nie sam obiekt — tylko instrukcja jak go stworzyć.

class Samochod:
    ...

auto = Samochod()  # ← dopiero TERAZ powstaje konkretny egzemplarz

Dlaczego używamy 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ęć".

Definicja klasy World
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

🔍 Jak działa ten kod — linia po linii

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.

❓ Częste błędy

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

🧱 Pojęcie nr 2 — metoda 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.

🧪 Uruchamiamy świat

(nic się nie dzieje — i to jest OK)

▶️ Oczekiwany wynik

Krok 00 | czas świata = 0.10 Krok 01 | czas świata = 0.20 Krok 02 | czas świata = 0.30 Krok 03 | czas świata = 0.40 Krok 04 | czas świata = 0.50 Krok 05 | czas świata = 0.60 Krok 06 | czas świata = 0.70 Krok 07 | czas świata = 0.80 Krok 08 | czas świata = 0.90 Krok 09 | czas świata = 1.00

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.

Uruchomienie świata - 10 kroków
world = World(dt=0.1)

for i in range(10):
    world.step()
    print(f"Krok {i:02d} | czas świata = {world.time:.2f}")

🔍 Jak działa ten kod — linia po linii

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

🧠 Ważna obserwacja: Czas w symulacji to umowa, nie prawda absolutna. Świat wie tylko że wykonał N kroków — nie wie czym jest „sekunda".

📊 Pojęcie nr 3 — lista []

(czyli: jak świat pamięta przeszłość)

💡 Dla nowicjusza

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.

📊 Wizualizacja czasu

💡 Dla nowicjusza — co to jest 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 🔬

▶️ Oczekiwany wynik

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.

💾 Export do CSV — po co i jak?

Po co w ogóle CSV?

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:

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

🔍 Jak działa ten kod — linia po linii

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

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ść dt

Symulacja nie płynie. Symulacja przeskakuje.
Mniejsze dt → gęstsze kropki → dokładniejsza symulacja.

🧪 Eksperymenty

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

🟢 Eksperyment 1 — podejrzyj historię

⚠️ 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:

[0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0, 2.25, 2.5]

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

🟢 Eksperyment 2 — kolejność ma znaczenie

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:

Oryginał: [0.0, 0.25, 0.5, 0.75, 1.0] V2: [0.0, 0.0, 0.25, 0.5, 0.75]

Szczegółowe wyjaśnienie:

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

🟡 Eksperyment 3 — dwie listy jednocześnie

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:

history: [0.0, 0.25, 0.5, 0.75, 1.0] steps: [0, 1, 2, 3, 4] Liczba kroków: 4

Szczegółowe wyjaśnienie: len(self.steps) przed append zwraca aktualną długość listy — to jednocześnie numer nowego elementu.

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.

🔴 Eksperyment 4 — porównanie 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:

Oba wykonały 20 kroków: dt=0.05 → czas końcowy: 1.00s (20 × 0.05) dt=0.50 → czas końcowy: 10.00s (20 × 0.50)

Wykres: linia niebieska gęsta (21 punktów blisko siebie), linia pomarańczowa rzadka (21 punktów daleko od siebie).

Szczegółowe wyjaśnienie:

Dlaczego 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ść.

🧱 Architektura po Lekcji 00

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

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

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.

🧠 Podsumowanie Lekcji 00

Po tej lekcji mamy:

🔜 Co w Lekcji 01?

👉 Lekcja 01 — Object i stan

Dodamy:

🧠 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ć. 🚀


📝 Dodatek — for, print i f-stringi

🔁 Pętla for

💡 Dla nowicjusza

Pę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

🧵 F-string — formatowanie tekstu

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.

Formatowanie liczb:

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.

▶️ Oczekiwany wynik poniższego kodu

KROK | CZAS ŚWIATA | STATUS ----------------------------------- #01 | 0.250 | ✅ OK #02 | 0.500 | ✅ OK #03 | 0.750 | ✅ OK #04 | 1.000 | ✅ OK #05 | 1.250 | ✅ OK
Przykład formatowania f-string
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")

📋 Ściągawka — Lekcja 00

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

✅ Checklista — Świat, który jeszcze nic nie robi

Sprawdź, czy naprawdę rozumiesz:

🎯 Zadania z charakterem

🟢 Zadanie 1 — Fundament czasu

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

🟢 Zadanie 2 — Rola dt

Pytania:

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?

🟡 Zadanie 3 — Metoda step()

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

🟡 Zadanie 4 — Historia

⚠️ 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

🔴 Zadanie 5 — Świat bez fizyki

W tej lekcji świat nie ma: prędkości, przyspieszenia, sił, obiektów.

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?

🏆 Zadanie mistrzowskie — World to tylko zegar

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.

✅ Odpowiedzi — Checklista

  1. TakWorld posiada atrybut time, tworzony w __init__ przez self.time = 0.0.
  2. Taktime = 0.0 bo każda symulacja zaczyna od chwili „zero" — jak stoper przed startem wyścigu.
  3. Takdt 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.
  4. Takstep() wykonuje dokładnie self.time += self.dt. To jedyna operacja arytmetyczna w całej lekcji.
  5. Taktime jest modyfikowane wyłącznie wewnątrz step(). W żadnym innym miejscu nie piszemy self.time = .... To ważna zasada enkapsulacji.
  6. Takhistory to lista []; self.history.append(self.time) dodaje wartość na koniec listy po każdym wywołaniu step().
  7. Tak — brak atrybutów: x, y, vx, vy, ax, ay. Świat L00 nie wie nic o przestrzeni — tylko o czasie.
  8. Tak — brak listy objects = []. Świat L00 nie zarządza jeszcze żadnymi bytami w przestrzeni.
  9. Tak — jedyna zmiana stanu to time += dt i zapis do history. Wszystko inne jest statyczne lub nie istnieje.
  10. TakWorld to deterministyczny licznik. Przy tych samych parametrach i tej samej liczbie kroków zawsze daje identyczny wynik. Zero losowości, zero fizyki.
  11. 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.
  12. Przy tych samych 20 krokach: 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.
  13. Tak — wystarczy napisać: for _ in range(100): world.step(). Podkreślenie _ zamiast zmiennej i jest konwencją Pythona gdy nie używamy licznika.

🎯 Odpowiedzi — Zadania z charakterem

🟢 Zadanie 1 — Fundament czasu

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

🟢 Zadanie 2 — Rola dt

dt 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ć.

🟡 Zadanie 3 — Metoda 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ł!

🟡 Zadanie 4 — Historia

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 historystep() 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, ...]

🔴 Zadanie 5 — Świat bez fizyki

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.

🏆 Zadanie mistrzowskie — World to tylko zegar

world1.time = 10.0 world2.time = 20.0

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.


🧠 Zasada Profesora

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.


🔥 Złota Myśl

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