2022.01.31. Scratch és Python
A Pythonnak és a Scratch-nek a következőkben alapvetően eltérő a filozófiája:
- A Scratch-ben van egy fix méretű vászon, míg a Pythonban ilyen nincs, ezt külön létre kell hozni.
- Ráadásul ez nem "beépített", ahhoz az ún. TK könyvtárat kell használni.
- A Scratch-ben vannak szereplők, a Pythonban nincsenek. Az utóbbit leginkább úgy lehet elképzelni, mintha a Scratch-ben minden kód egy fix helyen, mondjuk a háttéren lenne.
- A Scratch-ben az egyes szereplők programja párhuzamosan fut, ráadásul egy szereplőnek egyszerre több programja is lehet, amelyek szintén párhuzamosan futnak. Pythonban alapból összesen egy főprogram van. A párhuzamosságot vagy szimuláljuk, vagy ún. szálakat (thread) használunk.
Szereplők mozgatása Pythonban a TK vásznon lényegesen bonyolultabb, mint a Scratch-ben. Néhány lényeges különbség a Scratch vászon és a TK vászon között:
- A Scratch-ben a vászon közepén van a (0, 0) pont, míg TK-ban a vászon bal felső sarkában.
- A Scratch-ben az y koordináta fentről lefelé csökken (180-ról -180-ra), míg TK-ban fentről lefelé növekszik (0-ról a vászon függőleges méretéig).
- A Scratch-ben az irányt fokokban számítjuk. A Pythonban (ez tehát Python tulajdonság, nem TK) radiánban.
- A Scratch-ben forgásnál a pozitív irány az óramutató járásával megegyező, Pythonban pedig (ahogy általában a matematikában) azzal ellentétes.
- A Scratch-ben a 0 fok felfelé mutat, a Pythonban jobbra.
- A Scratch-ben a szereplők forgatása alapművelet. A Pythonban annyira nem az, hogy még a TK sem tudja alapból, ahhoz a Pillow nevű külső könyvtárat kell használnunk. Ráadásul Python-ban nem is lehet szereplőket forgatni, hanem a forgatást úgy lehet csak szimulálni, hogy letöröljük a korábbit, és újra kirajzoljuk.
- A Scratch-ben tudjuk a szereplőket abszolút pozícióba mozgatni, míg TK-ban csak a változást tudjuk megadni.
Ennyi koncepcionális eltérésnél nem igazán érdemes olyan táblázatot készíteni, ami a Scratch parancsok Python megfelelőit tartalmazza, ugyanis annak csak megfelelően beültetett programrészletek feleltethetőek meg. Emiatt inkább néhány gyakoribb kódrészletet nézünk meg Scratch-ben és Pythonban.
A példákhoz szükség lesz egy képre. Töltsük le az alábbit:
Ha jobban szemügyre vesszük, akkor láthatjuk, hogy alul,, felül, jobb és bal oldalon is van egy üres sáv. Erre a forgatás miatt van szükség: hogy elfogatva is kiférjen.
Szereplő mozgatása jobbra, balra, előre, hátra
Az egyik leggyakoribb Scratch program váza az alábbi:
A fenti példának megfelelő Python tinker kód:
from tkinter import * def key_pressed(event): scratch_cat_coord = canvas.coords(scratch_cat) if event.keysym == 'Left' and scratch_cat_coord[0] > 50: canvas.move(scratch_cat, -5, 0) if event.keysym == 'Right' and scratch_cat_coord[0] <= 430: canvas.move(scratch_cat, 5, 0) if event.keysym == 'Up' and scratch_cat_coord[1] > 50: canvas.move(scratch_cat, 0, -5) if event.keysym == 'Down' and scratch_cat_coord[1] <= 310: canvas.move(scratch_cat, 0, 5) root = Tk() canvas = Canvas(master=root, width=480, height=360, bg='white') canvas.pack() scratch_cat_image = PhotoImage(file='scratch_cat.png') scratch_cat = canvas.create_image(240, 180, image=scratch_cat_image) root.bind('<KeyPress>', key_pressed) root.mainloop()
Magyarázat:
- from tkinter import *: betöltjük a TK komponenseket.
- root = Tk(): létrehozzuk a TK programot.
- canvas = Canvas(master=root, width=480, height=360, bg='white'): létrehozunk egy akkora fehér vásznat, amekkora a Scratch-ben van.
- canvas.pack(): láthatóvá tesszük a vásznat. (Sajnos sok ehhez hasonló felesleges kódot kell írni.)
- scratch_cat_image = PhotoImage(file='scratch_cat.png'): betöltjük a képet. Ne feledjük: alapból a Pythonban nincsenek szereplők, azokat be kell tölteni. Gondoskodjunk arról, hogy a kép megfelelő könyvtárban legyen.
- scratch_cat = canvas.create_image(240, 180, image=scratch_cat_image): ezzel létre hozzuk a létható képet. Tehát az előző magára a png képre hivatkozik, ez pedig arra, ami a vásznon látszik.
- root.bind('<KeyPress>', key_pressed): ezzel mondjuk meg azt, hogy mi történjen billentyű lenyomáskor.
- root.mainloop(): enélkül néha működik, néha nem, így ki kell írni.
- def key_pressed(event):: ez fut le billentyű lenyomáskor.
- scratch_cat_coord = canvas.coords(scratch_cat): lekérdezi a macska aktuális koordinátáit. Erre amiatt van szükség, hogy megállapítsuk, elérte-e a szélét. Erre a Scratch-ben nem volt szükség, viszont egyben példát mutat arra, hogy hogyan tudjuk a szereplő koordinátáit (Scratch-ben: "x-hely", "y-hely") lekérdezni.
- if event.keysym == 'Left' and scratch_cat_coord[0] > 50:: ha a bal gomb van lenyomva, és az x koordináta nagyobb mint 50.
- canvas.move(scratch_cat, -5, 0): a vízszintes tengelyen -5 képpontnyit mozgassuk, a függőleges tengelyen pedig 0 képpontnyit.
- A következő 6 sor a jobbra, fel ill. le mozgatás.
Szereplő forgatása és mozgatása
Tekintsük a következő programot, melyben a szereplőt jobbra-balra forgathatjuk, valamint előre-hátra mozgathatjuk:
Szereplőt forgatni Pythonban nem lehet. A megoldás a következő: le kell törölni a korábbit, és újra ki kell rajzolni. Kép esetén ez annyiban más, hogy magát a képet kell elforgatni, majd letörölni és újra kirajzolni, csakhogy képet alapból sem a Python, sem a TK nem tud, ahhoz egy külső könyvtárat kell használnunk. Sajnos sokkal rosszabb eredményt ad, mint a Scratch, így nem is azt fogjuk csinálni, hogy forgatjuk, majd a forgatottat forgatjuk tovább, hanem minden lépésben kiszámoljuk, hogy a kezdeti állapotból mennyit kell forgatni, és mindig a kezdő képet forgatjuk. Az eredmény így is csúnya, de legalább nem annyira.
Az eredmény egészen elképesztően bonyolult lett:
import math from tkinter import * from PIL import Image, ImageTk # pip install Pillow def is_within_canvas(new_x, new_y): return new_x >= scratch_cat_size / 2 \ and new_x <= canvas_width - scratch_cat_size / 2 \ and new_y >= scratch_cat_size / 2 \ and new_y <= canvas_height - scratch_cat_size / 2 def bounce(new_x, new_y): if new_x < scratch_cat_size / 2: rotate_image(-2 * (scratch_cat_rotate_degrees - 90)) elif new_x > canvas_width - scratch_cat_size / 2: rotate_image(-2 * (scratch_cat_rotate_degrees - 90)) if new_y < scratch_cat_size / 2: rotate_image(-2 * scratch_cat_rotate_degrees) elif new_y > canvas_height - scratch_cat_size / 2: rotate_image(-2 * scratch_cat_rotate_degrees) def key_pressed(event): if event.keysym in ['Up', 'Down']: scratch_cat_rotate_radians = math.radians(scratch_cat_rotate_degrees) steps_a = speed * math.sin(scratch_cat_rotate_radians) steps_b = speed * math.cos(scratch_cat_rotate_radians) scratch_cat_coord = canvas.coords(scratch_cat) if event.keysym == 'Up': new_x = scratch_cat_coord[0] + steps_b new_y = scratch_cat_coord[1] - steps_a if is_within_canvas(new_x, new_y): canvas.move(scratch_cat, steps_b, -steps_a) else: bounce(new_x, new_y) if event.keysym == 'Down': new_x = scratch_cat_coord[0] - steps_b new_y = scratch_cat_coord[1] + steps_a if is_within_canvas(new_x, new_y): canvas.move(scratch_cat, -steps_b, steps_a) else: bounce(new_x, new_y) if event.keysym == 'Left': rotate_image(15) if event.keysym == 'Right': rotate_image(-15) def rotate_image(degrees, first_run = False): global scratch_cat_image global scratch_cat_tkimage global scratch_cat_rotate_degrees global scratch_cat scratch_cat_rotate_degrees = scratch_cat_rotate_degrees + degrees scratch_cat_image_rotated = scratch_cat_image.rotate(scratch_cat_rotate_degrees) scratch_cat_tkimage = ImageTk.PhotoImage(scratch_cat_image_rotated) scratch_cat_coords_x = canvas_width/2 scratch_cat_coords_y = canvas_height/2 if not first_run: scratch_cat_coords = canvas.coords(scratch_cat) scratch_cat_coords_x = scratch_cat_coords[0] scratch_cat_coords_y = scratch_cat_coords[1] canvas.delete(scratch_cat) scratch_cat = canvas.create_image(scratch_cat_coords_x, scratch_cat_coords_y, image=scratch_cat_tkimage, tags = 'scratch_cat_image') speed = 5 scratch_cat_rotate_degrees = 0 root = Tk() canvas_width = 480 canvas_height = 360 scratch_cat_size = 100 canvas = Canvas(master=root, width=canvas_width, height=canvas_height, bg='white') canvas.pack() scratch_cat_image = Image.open('scratch_cat.png') rotate_image(0, True) root.bind('<KeyPress>', key_pressed) root.mainloop()
Néhány fontosabb részt kiemelek:
- A scratch_cat_rotate_degrees globális változóban tároljuk a macska irányát, és felhasználjuk, ahol kell.
- A "menj 5 lépést" utasításnak megfelelő rész a key_pressed függvényben a mat.sin() ill. math.cos() rész, melyben átszámoljuk a pillanatnyi irányt radiánná, és trigonometrikus függvények segítségével kiszámoljuk az új koordinátákat.
- Nem a TK PhotoImage osztályát használjuk a kép betöltésére, hanem a Pillow Image osztályát, amit el tudunk forgatni. Ezt átalakítjuk a ImageTk osztály segítségével olyanná, amit a TK vászon is meg tud jeleníteni.
- A bounce() a "ha szélen vagy, pattanj vissza" megfelelője. Itt minden esetre pontosan ki kell számolni az új irányt.
- Persze azt is ki kell számolni, hogy szélen vagyunk-e; ez az is_within_canvas() függvény.
Megjegyzések
Megjegyzés küldése