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.
(TK -> Tkinter a Python GUI eszközkészlete)

MOZGATÁS

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:

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

Népszerű bejegyzések ezen a blogon

Ágazati alapvizsga (Programozás)

Ágazati alap vizsga (WEB)

HTML + CSS (elmélet+gyakrolat)