ITV0110 3. töö 2017: python ja cgi

Allikas: Lambda
Python


NB!

Keerulise osa realiseerimisest: vaata uusi alapeatükke 7.7 ja 7.8 allpool.

Mis tuleb teha

Sinu ülesandeks on ehitada veebisüsteem, mille kaudu saab teises praksis realiseeritud minesweeperi klooni tulemusi ja seisu salvestada ja vaadata/taastada, ehk siis teise praktikumi teema edasiarendus serverirakendusena.

Ülesandel on kolm osa lihtsamast keerulisemani:

  • Arvestuseks kohustuslik: sinu veebirakendus peab serverisse salvestama iga lõppenud mängu tulemuse: mängija nime, laua suuruse, pommide arvu, tulemuse (võit/kaotus) ja tehtud käikude arvu. Samuti pead tegema serveriprogrammi, mis tulemustabeli ilusa veebilehena välja kuvab.
  • Annab olulise osas punkte, aga ei ole arvestuseks kohustuslik: saad mängides serverisse salvestada hetkeseisu ja hiljem suvalisel hetkel serverist selle taastada ja edasi mängida.
  • Keeruline ja rangelt vabatahtlik, annab ühe punkti: mõtled välja ja ehitad viisi, kuidas kaks mängijat võidu mängivad samal alguslaual.

Tulemuste salvestamine serverisse ja pärast väljakuvamine

  • Mängimist alustades pead vormi väljalt sisestama oma nime: edaspidi jäägu nimi javascripti mällu meelde (või pane ta kaasa serverist tulevas vastuses), et ei peaks uuesti sisetama, kuni leht pole uuesti laetud või kui ei vajutata "reset" nuppu vms.
  • Mängu lõppedes pead tegema ajaxi-päringu serveris olevale pythonis cgi-rakendusele, kes siis tulemuse faili lisab. Kasutajale ei ole vaja salvestamise kohta midagi näidata.
  • Pead tegema pythonis eraldi serverirakenduse, mis kuvab kena veebilehena välja tulemuste tabeli esimesed 100 rida. Samuti peab sellel veebilehel olema otsivorm, kuhu saad sisestada mängija nime: siis kuvatakse ainult selle nimega mängija tulemused. Las see pythoni rakendus lihtsalt loeb andmed failist ja trükib välja sobiva html-i.
  • Mängu põhilehele tee nupp, millele vajutades avatakse eelpoolkirjeldatud tulemuste leht.

Pooliku mängu salvestamine serverisse ja pärast taasalustamine

  • Tee oma põhi-mängu-lehele nupud "Salvesta seis" ja "Loe salvestatud seis".
  • Salvestamise nupule vajutamise peale saada kogu mängu-info, sh laua sisu, koos mängija nimega ajaxis serveris olevale pythoni cgi-le, kes siis selle mängijaga seotult faili salvestab.
  • Lugemise nupule vajutades küsi serveris olevalt pythoni cgi-lt praeguse mängija poolt (viimati) salvestatud seisu, kuva selle seisu laud ja lase jätkata samast kohast.

Sul ei ole vaja salvestada ühe inimese jaoks mitut eri mängu.

Teise inimese vastu mängimine

Ära hakka seda osa tegema, kuni esimesed pole täiesti valmis. See osa on mõeldud juhuks, kui tahad teha midagi keerulisemat.

Täpset spetsifikatsiooni, kuidas teise inimese vastu mängimine käib ja kuidas seda teha, ei antagi. Pead selle ise välja mõtlema.

NB! Kasuks on uurida Rogeri chati-näidet: Fail:Python simple chat.zip: see annab ideid praksi kahe-mängija variandi tegemiseks ja demob jquery kasutamist ajaxi päringute tegemiseks.

Kuidas serverisse tulemusi salvestada ja sealt kuvada

Serverisse andmete salvestamiseks kirjuta nad faili.

Mängutulemusi on kõige lihtsam kirjutada ühe reana faili lõppu juurde. Poolikut mängu on kõige lihtsam salvestada jsoni kujul eraldi faili, mille nimi sisaldab mängija nime.

Kõigi mängude korral on vaja faili salvestada ka inimese nimi, kes mängis. Seega on mängu alustades vaja vormilt sisestada oma nimi, mis siis serveris mängufaili kirjutatakse ja mis mängu ajal ära ei muutu. Nime võib kasutaja valida vabalt, paroole, autentimist jne jne ei ole vaja teha.

Praktikumi arvestamine ja hindamine

Praktikum annab maksimaalselt 12 punkti.

Esimene osa (lõppenud mängude salvestamine serverisse ja seni mängitud mängude tulemuste kuvamine) on kohustuslik osa praktikumist ja nende korralik realiseerimine annab 6 punkti. Praktikumi ei arvestata, kui selles osas on suuremad puudujäägid.

Teine osa (pooleli mängude salvestamine serverisse ja hilisem jätkamine) ei ole arvestuseks kohustuslik, tema korralik realiseerimine annab 5 punkti.

Kolmas osa - mängijate omavahelise mängu realiseerimine - ei ole hoopiski kohustuslik ja annab ühe punkti.

Pane tähele, et selles praktikumis on kõigi ülesannete realiseerimiseks vajalik koodihulk väike, samas nõuab tõsist pingutust välja mõelda, kuidas kõiki asju täpselt teha ja esitada ja kuidas nad omavahel kokku käivad.

Tehnoloogilised nõuded

  • Kõik serverirakendused tuleb realiseerida Pythonis.
  • Rakendus peab olema võrgus vabalt brauseriga ligipääsetav prax3 kataloogis, mitte lihtsalt töötama näiteks sinu laptopis.
  • Serverarvutina tuleb kasutada dijkstrat, rakenduse failid tuleb kopeerida kataloogidesse public_html/prax3 ja public_html/cgi-bin/prax3
  • Serveris tuleb infot hoida ühes või mitmes mängutulemuste failis.
  • Kasutada tuleb nimelt lihtsaid inimloetavaid faile, andmebaasimootoreid kasutada ei tohi (andmebaasimootorite jaoks on neljas praks).
  • Loodud veebilehed peavad sisaldama html-i, css-i ja javascripti (pluss pilte), javat ja flashi jne kasutada ei tohi.

Turvaküsimustega ei pea selles praksis tegelema! Seega on täiesti ok, kui mängija saab ise anda cgi-le parameetrid näiteks võõra mängu kohta ja sellega teiste mängu ära rikkuda, pettusi teha jne. Turvaküsimused oleks selle praksi jaoks lihtsalt liiga töö- ja tähelepanumahukad.

Dijkstra server

Dijktra serverisse ligipääsu, failide laadimise ja linuxi käsureaga hakkamasaamise kohta loe pikemalt dijkstra serveri kasutamise õpetust.

Oma programmid pane kataloogi public_html/cgi-bin/prax3 . Kõigepealt pead selle kataloogi tegema ja talle grupile ligipääsuõiguse andma, analoogiliselt teise praktikumiga. Mujale pandud programme Apache käima ei pane.

Kui töös taaskasutatakse teise praktikumi koodi, siis tee sellest uus koopia public_html/prax3 alla. Teise praktikumi töö peab ka alles jääma.

Samuti on sul vaja veidi tutvuda linuxi käsurea elementaarkäskudega (kopeerimine, kustutamine, õiguste muutmine jne) nagu teises praksis soovitatud.

Soovitused programmeerimiseks

Kõigepeal tutvu cgi programmide kirjutamise esmaste põhimõtetega.

Seejärel vaata kindlasti Kasulikke pythoni näitejuppe cgi jaoks. Nendes näidetes toodud konstruktsioonidest ja funktsioonidest peaks kolmanda praksi jaoks piisama.

CGI põhimõtetest paremaks arusaamiseks tasub lisaks tutvuda cgi näidetega loengust.

Python

Kui sa ei ole varem Pythonis cgi-sid programmeerinud, tutvu kõigepealt Pythoni keele ja cgi teegiga:

Hea mõte on paralleelselt teha lahti Pythoni interpretaator ja katsetada kõrvale asju otse Pythoni käsureal. Kui su masinas Pythonit pole, siis installeeri!

Paljud kasulikud asjad on kirjas Pythoni teegi dokumentatsioonis. Konkreetsete küsimuste korral aitab tihti googeldamine.

NB! Pythonil on väga palju teeke ja mitmeid mittetriviaalseid keelekonstruktsioone. Sul ei lähe rõhuvat enamust nendest vaja, praktikumiks piisab väga esmastest asjadest (listid, stringid, numbrid) ning teekidest ei lähe samuti muud vaja, kui failide lugemine/kirjutamine, stringioperatsioonid ja cgi. Ära kuluta kohe aega kõigi Pythoni teekide ja detailide uurimisele, pigem alusta peale esmast tutvust koodi kirjutamist.

Arvesta, et dijkstra serveris on installeeritud Python 2.7.3, seega väldi Python 3 spetsiifiliste asjade kasutamist (vt erinevusi 2 ja 3 sarja vahel), muidu võib tekkida komplikatsioone programmide viimisel dijkstrasse. Kui sa midagi eriti keerulist ei tee, siis ei tohiks 3 sarja programmide ja 2 sarja interpretaatoriga siiski suuremaid probleeme tekkida.

Kirjuta mõni väike näiteprogramm ja katseta serveris, kuhu lõpuks süsteemi üles paned (kas siis dijkstra või oma isiklik lemmikserver).

Python + CGI

Vormidelt saadetud data kasutamiseks on Pythonis tore imporditav moodul nimega cgi, mille funktsioonid aitavad vormiinfot söödavamal kujul sisse võtta.

Looge fail test2.py:

#!/usr/bin/python
import cgi

print "Content-type: text/html"
print

print "<html><head><title>test2.py</title></head><body><h1>Hello, World!</h1><p>Lisaparameeter oli "

formdata = cgi.FieldStorage()
if formdata.has_key("lisaparameeter"):
  print formdata['lisaparameeter'].value
else:
  print "puudu"

print ".</p></body>"

Vaadake faili brauseris:
http://dijkstra.cs.ttu.ee/~t0XXX/cgi-bin/test2.py

Parandage faili õigused omaette kirudes, et seda unustati teile teisel korral öelda ja vaadake uuesti.

http://dijkstra.cs.ttu.ee/~t0XXX/cgi-bin/test2.py http://dijkstra.cs.ttu.ee/~t0XXX/cgi-bin/test2.py?lisaparameeter=olemas

Tasub vaadata ka Kasulikke pythoni näitejuppe cgi jaoks.

Arendamine oma või arvutiklassi arvutitel

Kuna sinu süsteem peab lõpuks töötama avalikul veebiserveril, kuid arendust teed oma või arvutiklassi arvutitel, siis kuidas oleks mõistlik tööd korralda?

Peamised variandid on sellised:

  • Kirjutad faile oma masinas redaktoriga, mis lubab üle võrgu faile avada ja sulgeda, windowsis näiteks winscp abil. Katsetamiseks ja testimiseks hoiad lahti sisse logitud aknaid Putty-ga. See on suhteliselt mugav viis kui sa ei taha hiljem kirjeldatud meetodeid - mis veidi pikemas perspektiivis on paremad - kasutada.
  • Kirjutad kõik otse serveris, kasutades sisselogimiseks Putty-t (windows) või ssh-d (linux) ja serveris kasutad redaktoriks näiteks vi-d. Aitab, kui teed lahti mitu akent. Kõige universaalsem viis, aga suhteliselt ebamugav, samuti nõuab vi õppimist, mis võtab aega. Võimalik alternatiiv vi-le serveris on nano.
  • Arendad süsteemi võimalikult lõpuni windowsis valmis ja siis kannad üle serverisse ja teed vajalikke muutusi. Selleks installeeri oma arvutis python windowsile, arvutiklassides peaks see juba olemas olema. Väga abiks oleks seejuures ka veebiserveri installeerimine, näiteks
  • Kui sul on oma arvutis windows ja põhiliselt kasutad seda, siis kõige soovitavam ja pikemas perspektiivis mõistlikum viis on installeerida windowsile lisaks ubuntu linux
  • Ja loomulikult on võimalik kasutada oma masinas mõnda standalone unixt: linuxit, mõnda bsd-d, mac os X vms kas ainsa süsteemina või dual bootina. Kui kirjutad palju ja tihti koodi linuxi serverite jaoks, siis on see tõenäoliselt kõige mõistlikum variant.

Tegelik koodikirjutamine

Kõige olulisem soovitus: alusta ülesande esimesest osast - oma mängu tulemuse salvestamine serverisse - ja tee seejärel valmis salvestatud faili lihtne kuvamine brauseris.

Kui see korralikult käib, siis täienda esialgset funktsionaalsust vastavalt nõuetele ülal.

Ära hakka korraga palju funktsionaalsust programmeerima: tee üks väike osa juurde, katseta läbi ja paranda, ning alles seejärel lisa järgmine tükk funktsionaalsust jne.

Ehk, selles järjekorras:

  • väike katseprogramm a la hello world (võid ta kopeerida ülal viidatud koodinäidetest)
  • oma mängu tulemuse salvestamine serveris olevasse faili
  • tulemusfaili kuvamine
  • pooleli mängu salvestamine serverisse
  • salvestatud mängu jätkamine
  • vastase vastu mängimine

Süsteemis on hulk funktsionaalsust, mis nõuaks justkui hulga erinevate cgi-de tegemist. Üks variant süsteemi ehitada ongi teha mitu cgi-d, teine - tihti parem - variant on aga teha üks (või paar) cgi-d, mis saavad vormidelt ette hidden väljale pandud operatsiooninime (a la myprog.cgi?op=storetofile&name=John ...) ja siis kutsuvad välja operatsiooninimele vastavat funktsiooni sellessamas cgi-s.

HTML tekst programmis või eraldi failides

Kuna sinu programm peab kokku panema ja lõpuks välja trükkima hulga html teksti, tekib küsimus, et kuidas seda paremini teha. Üldiselt on siin järgmised erinevad viisid või põhimõtted:

  • Kõige lihtsam ja väga väikeste programmide korral kõige mugavam on panna html teksti jupid otse programmi muutujate väärtusteks umbes nii (näide on väga lihtsustatud, praktikas oleks juppe ja muutujaid rohkem):
...
myresult=2
yourresult=1
header="<html><body>"
html1="minu tulemus on "
html2=" ja sinu tulemus on "
footer="</body></html>"
res=header+html1+str(myresult)+html2+str(yourresult)+footer
print res
  • Kui htmli on rohkem ja programm veidi keerulisem, siis on vastupidi, praktilisem mitte panna kõiki htmli juppe programmi sisse, vaid lugeda html tekst eraldi failist ja siis asendada hulga lõike/juppe hetkel vajalikuks. Asendamiseks mõtle lihtsalt välja endale meeldivad marker-tekstijupid, mida htmlis mujal ei esine, ja tee stringiasendusi. Näiteks, kui su html on failis kpktemplate.html ja oled sinna pannud markerid {myresult} ja {yourresult} siis näeks kasutus välja umbes nii:
...
myresult=2
yourresult=1
f=open('kpktemplate.html','r')
template=f.read()
f.close()
template=template.replace("{myresult}",str(myresult))
template=template.replace("{yourresult}",str(yourresult))
print template
  • Sellise lihtsa stringiasenduse asemel võib kasutada pythonisse sisse ehitatud eraldi templatefunktsioone või ka keerulisemaid ja vägevamaid väliseid templateteeke (mako jms), mis teevad suurtes kogustes asendamisi efektiivsemalt ja mugavamalt ja lubavad template sisse programmijuppe kirjutada. Meie väikese praksiülesande jaoks oleks need vägevamad templatesüsteemid aga pigem overkill, st saadud lisamugavused asenduste tegemiseks tõenäoliselt ei kaaluks üles ajakulu/pingutust nende süsteemide uurimisele.

Andmete esitamine ja kasutamine serveris

Info salvestamise failid on soovitav luua õigustega, kus kõik võivad faili lugeda ja kirjutada. See on turvalisuse mõttes halb, aga arendamise/debugimise mõttes hea. Siis saad ligi ise ja saavad ligi sinu programmid. Käsurealt saad teha nii:

    chmod a+rw myfile.txt

Täiesti võimalik on olukord, kus Apache poolt käivitatav cgi programm ei ole nõus sinu kataloogis uusi faile tegema: siis tuleks kas muuta oma kataloogi õigusi kõigile loetavaks/kirjutatavaks või teha failid mõnda kataloogi, kus apachel on õigused (ntx /tmp, kuid siis tuleks nende nimele panna ntx sufiksiks sinu matriklinumber, et teistega segi ei läheks).

Failile kirje lisamiseks võid kirjutada uue kirje lihtsalt faili lõppu uueks reaks. Vanu kirjeid pole kunagi vaja muuta!

Igale kirjele võib panna lisaväljana (ntx esimeseks) kaasa kirje loomise aeg.

Faili lugemisel on üldiselt hea mõte kasutada f.readline() funktsiooni, võid soovi korral kasutada ka csv teeki, mille abil faili tabeliks lahti hakkimine nõuab veidi vähem koodikirjutamist (aga mitte tingimata palju vähem sinu aega).

Aja määramisel on sul kõige praktilisem kasutada time.time() funktsiooni (otsi see mahukast ajateegist välja: see annab lihtsalt aja ühe numbrina, sekundites peale aastat 1970 vms. Selle numbri saad siis lihtsalt faili kirjutada, kui tahad aega salvestada. Lugemisel on seda samuti lihtne võrrelda (ja liita/lahutada) hetkeajaga.

NB! Ära hakka jändama keerukate aasta/minuti/ajatsooni jne jne meetoditega: need on keerulised, nende uurimine võtab hulga aega ja praksis neid vaja ei lähe (mis ei tähenda, et huvi korral ei võiks neid uurida :).

Stringi muutmine numbriks on lihtne (int("12") või float("12")) ning samamoodi on lihtne numbri muutmine stringiks (string(12) või string(12.5)).

Kuidas saata serverisse andmeid otse jsoniga ilma cgi kodeeringuta

Põhimõte: postita serveriprogrammile otse jsonit.

Kuidas seda näiteks fetch meetodiga teha:

   fetch("http://dijkstra.cs.ttu.ee/~tammet/cgi-bin/fetch.py", {
      method: "post",
      body: JSON.stringify(data)
   }).then(r=>r.text()).then(handlestore);

kus data on saadetavad andmed ja handlestore teeb midagi serverist saadetud vastusega.

Kontrolli serveris, kas ühendus oli GET või POST meetodiga (jsoni jaoks kasuta post-i) ning loe ja parse saadud data:

  import os, json
  ...
  inmethod=os.environ['REQUEST_METHOD']
  if inmethod=="POST": # GET-iga ära jsonit saada
     indata=sys.stdin.read()   # loe kogu string nn standardinputist
     data=json.loads(indata)   # parse JSON
     ...

Ideid üksteise vastu mängimise jaoks

Näiteks nii:

Sama alguslaud. Käiakse vaheldumisi. Kes komistab pommi otsa, on kaotanud. Kes saab laua lahti, on võitnud.

  • Kuidas teada, kellega mängida? Alustan ja teen laua ja minu proge saveb selle serverisse minu nimega a la tanel_start.json Teine avab spetsiaalse urli või paneb htmlile # parameetri minu nimega tanel või on laual eraldi väli vastase jaoks ja nupp "lülitu mängu"
  • js laseb käia ainult oma käigu korral: kuidas teada, kas on minu kord? Kui käin (klikin ruudule) siis js kirjutab serverisse, et olen käinud ja jääb ootama, millal vastane käis. Näiteks: tanel_gamestate.json kirjutada kas "tanel" või "opponent" (alternatiiv: tanel_koer_gamestate.json) failis siis vaheldumisi kas tanel või opponent.
  • Ootan, et kas vastane on käinud: loen serverist mängu seisundit failist tanel_gamestate.json js loeb ajaxiga iga sekund faili tanel_gamestate.json (polling). Keerulised alternatiivid regulaarsele pollimisele: alternatiiv 1: long poll (server vastab ainult siis kui vaja, muidu ei vasta) alternatiiv 2: websockets


Näide: rogeri chatiprogramm

Vaata Fail:Python simple chat.zip mis on hea algusnäide kahe mängija vahelise mängu ehitamiseks ja jquery teegi kasutuse uurimiseks. Rogeri näide kasutab ajaxi päringute tegemiseks jquery-t.

Põhiprobleemid

Esimese asjana pane serveris käima väike katseprogramm, näiteks copy-paste see jupp kataloogi public_html/cgi-bin nime all test.py:

#!/usr/bin/python
print "Content-type: text/html"
print
print "tere!"


Logi serverisse sisse, pane ta prooviks käsurealt käima ja siis vaata brauserist ka, urlilt http://dijkstra.cs.ttu.ee/~Eesnimi.Perenimi/cgi-bin/test.py

Kui see ei õnnestu, on sul tõenäoliselt juhtunud mõni - või mitu - järgmistest vigadest:


a) Debugimiseks on vaja serverisse sisse logida ja käsurealt programm käima panna.

Sisse logimiseks loe dijkstra serveri kasutamise õpetust. Käimapanekuks:

  cd public_html/cgi-bin
  ./test.py

Kas läks käima? Kas trükkis content-type: text/html ja tühja rea selle järel?

Esimese asjana logi serverisse ja pane programm käsurealt käima!

b) Kui EI lähe üldse käima, st saate vea

  -bash: ./test.py: Permission denied

siis tuleb õigused panna proge jaoks õigeks:

  chmod a+x test.py

c) Annab sellise vea:

   -bash: ./test.py: /usr/bin/python^M: bad interpreter:
    No   such file or directory

Siis on sul rea lõpus lisaks line-feed sümbolile ka carriage-return. Korista ta niimoodi ära:

1. Ava scite menüüs View -> End of line või Notepad++ menüüs View -> Show symbol -> show end of line

2. Vaata, kas esimese rea lõpus on ainult LF (õige) või CR/LF (vale).

Viimasel juhul:

Scite jaoks:

1) Options -> Line end characters -> LF
2) Options -> Convert line end characters
3) OK? Kui jah, keera View -> end of line maha

Notepad++ jaoks:

1) Edit -> EOL conversion -> Unix/osX format
2) OK? Kui jah, keera View -> Show symbol -> show end of line maha

d) Annab mõne muu vea, millest paistab, et python käima ei läinud.

Sul võib olla rea alguses mingi veider sümbol, mida redaktor välja ei näita.

Kontrolli serveri poolelt vaadates, st olles serveris progega samas kataloogis, ütle

 less test.py

ja vaata, kas esimene rida paistab ok või on seal midagi veidrat. Kui on midagi veidrat, kustuta oma redaktoris esimene rida üldse maha või tee nullist pisi-katsefail ja lae uuesti üles. Siis vaata uuesti less-ga.

less-ga vaatamisest saad välja, kui vajutad tähte q (nagu quit)


e) Annab vea, mille teksti sees on "SyntaxError: invalid syntax"

Siis ongi sinu koodis süntaksiviga. Sama veateade ütleb mh, kus viga on (mitmendal real) ja näitab lühidalt kahtlast kohta koodis, umbes nii:


  File "./try.py", line 4
     if 1==2
         ^
  SyntaxError: invalid syntax


Süntaksiveaga programm kuvab veebist vaadates "Internal server error" (sest ta ei trüki "content-type: ..." rida)

Paranda süntaksiviga ära ja katseta käsurealt uuesti!


f) Annab sellise vea

  File "./test3.py", line 4
  SyntaxError: Non-ASCII character '\xfc' in file ./test3.py 
  on line 4, but no encoding declared; see  
  http://www.python.org   
  /peps/pep-0263.html for details
 

Pythonile tuleb öelda täpitähtede kodeering:

Pane algusse nii:

  #!/usr/bin/python
  # -*- coding: iso-8859-1 -*-
või

  #!/usr/bin/python
  # -*- coding: utf-8 -*- 


g) Käsurealt läheb OK käima, Content-type: text/html on olemas, järgmine rida on tühi, aga brauseris kuvatakse programmi source!

Sel juhul oled pannud programmi otse public_html või public_html/prax3/ alla vms, aga peaks olema public_html/cgi-bin või public_html/cgi-bin/prax3/ all. Tõsta ta õigesse kataloogi!


h) Käsurealt läheb OK käima,aga brauseris kuvatakse "Internal server error"

Kui varasemad võimalikud probleemid on lahendatud (panid käsurealt käima?) siis võiboll ei trüki programm kohe esimese reana "content-type: text/html" (ilma jutumärkideta) või ei trüki selle järele tühja rida.


i) Käsurealt läks käima, aga brauserist näen viga tekstiga "Forbidden .."

Umbes nii:

  Forbidden:
  You don't have permission to access /~Eesnimi.Perenimi/cgi-bin/test.py on this server.


Sel juhul ei ole sa - tõenäoliselt - oma programmi pannud kataloogi public_html/cgi-bin/ või on ta seal teise nimega: st, teda lihtsalt ei leita selle urli pealt. Teine variant, et sa ei ole teinud

  chmod a+x test.py




Veel huvitavat: kuidas hästi mängida

Minesweeperi heal tasemel mängimiseks on välja töötatud mitmeid meetodeid. Vaata näiteks seda lõputööd.

Eelmiste aastate versioonid

Eelmiste aastate versiooni, mis on mitmes osas sarnased, kuid siiski veidi erinevad: 2015 aasta, 2014 aasta, 2013 aasta, 2012 aasta, 2011 aasta.