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.
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.
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.
Najključniji dio koda je funkcija kojom se inicijalizira varijabla
player
iz izbora igrača.
Ovdje se može vidjeti da se koristi
eval
funkcija, kojom se
fighter
unos poziva kao funkcija, a
fighterType
kao argument.
Sukladno tome, može se vidjeti da su
fighteri
zapravo 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.
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
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
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,
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.
Primjer:
exec("a=0")
Pregledom funkcije za validaciju unosa (slika 6) vidi se da
exec
funkcija 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:
Greška je
invalid syntax
, koju baca parser
eval
funkcije.
Opisana ponašanja prikazana su na slici 10.
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
eval
parser odobriti, a
exec
izvršiti i promijeniti globalnu varijablu
s
.
Način da se to postigne je korištenjem Pythonovog “walrus” operatora (
:=
), koji je
expression
. On vraća rezultat evaluacije izraza, ali također postavlja evaluiranu vrijednost u varijablu.
Walrus operator evaluira
expression
poput
1 == 1
, što je
True
, i to postavlja kao vrijednost varijable. Walrus operator se može koristiti za promjenu vrijednosti globalne varijable
s
unutar
eval
ili
exec
.
Ako se stave zagrade oko njega, izraz:
(a:=3)
postaje validan
expression
, što je prikazano na slici 13.
Ovo je slučaj u kodu zadatka, jer se oko unosa dodaju zagrade (vidi sliku 14).
Kada su zagrade oko unosa, unutar njih mora biti valid
expression
, što
s=0
nije (vidi sliku 15).
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.
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")
.
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.
Pri izvršavanju u global scopeu, ovo je moguće.