This is an old revision of the document!
Elemental Fighters
Uz zadatak je dan i izvorni kod.
Spajanjem na zadatak, otvara se izbor borca, mogući izbor su 3 borca i 3 elementa za svakog borca.
{{ elemental_fighters:slika1.png?nolink&500 | Slika 1. - izbor borca}}
No, analizom koda u zadatku, može se zaključiti da zapravo ne postoji kombinacija borca koja bi ni u najsretnijem slučaju uspjela pobijediti sve neprijatelje i doći do flaga, znači da je potrebno nešto drugo napraviti jer izbor pravog borca ne postoji.
Analizom koda, zanimljiva je varijabla s, koja je “scale”, odnosno određuje koliko će se oslabiti ili ojačati neprijatelje.
{{ elemental_fighters:slika2.png?nolink&500 | Slika 2 - scale varijabla}}
Ova varijabla se kasnije u kodu množi s atributima neprijatelja i što je manja, neprijatelji su slabiji, što je veća, neprijatelji su jači.
Također zanimljiv dio koda je player default, koji definira varijablu player kao:
Milo("ice")
ako je player None.
{{ elemental_fighters:slika3.png?nolink&500 | Slika 3. - Player default}}
Najključniji dio koda je funkcija kojom se inicijalizira varijabla player iz izbora igrača.
{{ elemental_fighters:slika4.png?nolink&500 | Slika 4. - inicijalizacija player varijable iz igračevog izbora}}
Ovdje se može vidjeti da se koristi eval funkcija, kojom se unos fighter poziva kao funkcija, a unos fighterType kao argument.
Sukladno tome, može se vidjeti da su fighteri zapravo funkcije:
{{ elemental_fighters:slika5.png?nolink&500 | Slika 5. - fighter funkcije}}
Vrijednosti fighterType definirane su u rječniku te se parsiraju pri pozivanju fighter funkcije.
To znači da se unosom fighterType (“Odaberite element borca”) definira argument funkcije, a unosom fighter (“Odaberite borca”) definira funkcija kojoj će se argument proslijediti i koja će se izvršiti s proslijeđenim argumentom u eval funkciji.
No svaki unos prolazi kroz validate_input, koja je filter koji određuje koji su unosi dozvoljeni, a koji nisu.
{{ elemental_fighters:slika6.png?nolink&500 | Slika 6. - validate funkcija}}
Kako bismo provjerili da možemo pozvati proizvoljnu funkciju i argument, ako nemamo unos koji bi validate_function zabranio, možemo probati pozvati:
list("aaa")
Odaberite element borca (Ice, Fire, Acid): aaaa Odaberite borca (Zorn, Krev, Milo)list
{{ elemental_fighters:slika7.png?nolink&500 | Slika 7. - poziv list("aaa")}}
Vidimo da je program vratio listu u kojoj se nalaze svi znakovi iz stringa koji smo poslali kao prvi argument, te oko njih znakovi zagrada oko fighterType varijable (vidi sliku 4).
Slično, možemo pozvati:
len("aaa")
Odaberite element borca (Ice, Fire, Acid): aaa Odaberite borca (Zorn, Krev, Milo)len
{{ elemental_fighters:slika8.png?nolink&500 | Slika 8. - poziv len("aaa")}}
Vidimo da program vraća 5, što odgovara trima znakovima “a” koje smo poslali i dvama znakovima zagrada.
No flag nije zapisan niti u jednoj varijabli, nego je hardkodiran u zadnjem printu programa. Zato je potrebno osmisliti način kako pozvati odgovarajuću funkciju i argument da bi se program uspješno izvršio do kraja i ispisao flag.
Dobar pristup bio bi mijenjanje varijable s (vidi sliku 2), koja predstavlja skalu jačine protivnika. Ako bismo tu varijablu mogli smanjiti ili postaviti na 0, protivnici bi bili znatno slabiji. Činjenica da je input potrošen na mijenjanje varijable s umjesto na inicijalizaciju borca ne bi bila problem jer postoji default odabir borca (vidi sliku 3).
No cijeli unos igrača izvršava se unutar eval funkcije, koja je namijenjena za evaluiranje *expression*. *Expression* je izraz koji nakon izvršavanja vraća neku vrijednost — primjerice:
"2+2" → 4
len("(aaa)") → 5
Svaka vrijednost vraćena nakon izvršavanja izraza u eval funkciji bit će pridružena varijabli player (vidi sliku 4).
Potrebno je pronaći način kako mijenjati druge varijable u globalnom kontekstu programa, kao varijablu s, unutar eval funkcije, čiji se rezultat izvršavanja pridružuje varijabli player.
Osim *expression* u Pythonu, koji izvršava izraz i vraća vrijednost, postoji i *statement*, koji izvršava definiranu akciju, no ne vraća ništa.
*Statement* je, primjerice:
a = 0
No pošto *statement* ne vraća ništa, ovaj izraz:
b = (a = 0)
je invalidan Python kod; također:
eval("a = 0")
je invalidan Python kod.
Slično eval, postoji funkcija exec, koja je namijenjena za izvršavanje *statement* (ali može izvršavati i *expression*), i može definirati ili mijenjati varijable u globalnom kontekstu ako se pokreće u globalnom kontekstu.
exec("a=0")
Pregledom funkcije za validaciju unosa (slika 6) vidi se da funkcija exec nije zabranjena. Dakle, exec se može koristiti za mijenjanje vrijednosti varijable s.
Iz ovoga se može zaključiti da potencijalni način rješavanja ovog programa bi bio izvršiti:
exec(s=0)
Unos bi izgledao ovako:
Odaberite element borca (Ice, Fire, Acid): s=0 Odaberite borca (Zorn, Krev, Milo)exec
Pokretanjem ovog unosa pojavljuje se greška:
{{ elemental_fighters:slika9.png?nolink&500 | Slika 9. - greška pri pokušaju}}
Greška je invalid syntax, koju baca parser eval funkcije.
Opisana ponašanja prikazana su na slici 10.
{{ elemental_fighters:slika10.png?nolink&500 | Slika 10. - eval parser i exec funkcija}}
Na slici je prikazano da eval može izvršiti:
a==3
jer je to validan expression koji se evaluira u False.
Može se zaključiti da je cilj napisati kod koji će parser eval odobriti, a koji će imati logiku jednaku s=0 statement. Ovdje pomaže činjenica da exec može izvršavati expression, pa treba napisati expression koji će parser eval odobriti, a funkcija exec izvršiti i promijeniti globalnu varijablu s.
Način da se to postigne je korištenjem Pythonovog “walrus” operatora:
:=
koji je expression. Evaluira izraz, vraća vrijednost, i postavlja je u varijablu.
(a:=3)
Ovo je slučaj u kodu zadatka, jer se oko unosa dodaju zagrade (vidi sliku 14).
{{ elemental_fighters:slika14.png?nolink&500 | Slika 14. - zagrade oko korisničkog unosa}}
Kada su zagrade oko unosa, unutar njih mora biti valid expression, što s=0 nije (vidi sliku 15).
{{ elemental_fighters:slika15.png?nolink&500 | Slika 15. - nevaljajuća sintaksa}}
Zato rješenje:
Odaberite element borca (Ice, Fire, Acid): s=0 Odaberite borca (Zorn, Krev, Milo)milo
nije moguće.
Ali walrus operator je validan expression i može se izvršiti unutar eval i exec, mijenjajući vrijednost globalne varijable.
{{ elemental_fighters:slika16.png?nolink&500 | Slika 16. - walrus i eval / exec}}
Rješenje se može unijeti ovako:
Odaberite element borca (Ice, Fire, Acid): s:=0 Odaberite borca (Zorn, Krev, Milo)exec
Izvršava se prethodno opisani postupak, varijabla s postaje 0, player ostaje None, te se inicijalizira default borac:
Milo("ice")
{{ elemental_fighters:slika21.png?nolink&500 | Slika 21. - rješenje zadatka}}
Eval / Exec i global scope:
Kada se izvršava u global scopeu, exec može promijeniti vrijednost varijable s. U local scope funkcije to ne bi bilo moguće.
{{ elemental_fighters:slika22.png?nolink&500 | Slika 22. - eval unutar functiona}}
Pri izvršavanju u global scopeu, ovo je moguće.
{{ elemental_fighters:slika23.png?nolink&500 | Slika 23. - eval unutar global scopea}}
</code>