september 14, 2014

TRIPS-TRAPS-TRULL: Variant III

Tere jälle, mänguhuvilised! Sain oma uusversiooni nüüd ilusti blogisse ja katsun õpinädalal nr 3 seda natukene kommenteerida ja ehk täiendada.
See ei ole veel OO versioon, nii et küsimus jääb - mis kasu võiks olla OO-st kodeerimisel?
Ka mulle on see küsimus veel peavalu valmistav. Ei ole mõtet tegelda vahendiga vahendi pärast, sellest peab siginema selget kasu.
Muidugi on täiesti võimatu tänapäeva meeskonnatöös mitte tunda OO-d ja mitte seda rakendada, aga alustuseks peab olema selge, mis kasu sellisest asjast üldse on.
Kanvaasid on endiselt 9 - jälle koht, mis veel vajaks muutmist. Järgmiseks, s.t. 4. nädalaks loodaksin jõuda variandini, kus mängulauaks võiks olla ka 4*4 mängulaud.
Arvuti õpetasin mängima (18.09.2014) kahel tasemel, "koolilapse" tase, ning tase, kus kõik variandid läbi vaadatakse. Et veidi huvitamav oleks, valitakse tase juhuslikult (ka siis, kui arvutid omavahel rammu katsuvad).
Aga kõige tähtsamaks probleemiks sai siiski selle versiooni juurs toimiva loogika väljamõtlemine ja siin enam ilma plokkskeemideta viisakalt hakkama ei saa. (Märkus 18.09.2014: praegu annan plokkskeemide osas ajutiselt alla. Proovin praegu lihtsalt koodiridu selgitada. Ma kasutan oma töö algusfaasis plokkskeeme, kuid need kritseldused hiljem ei ole isegi enesele arusaadavad. Kui nüüd hakata takkajärele plokkskeeme vormistama, siis see lihtsalt oleks sama hea, kui oma koodi dokumenteerida jumalikult kõrguselt. Aga algselt see töövahend ei ole selleks mõeldud, vaid selleks, et asjad selgeks mõelda, tööga alustada.
Seevastu positiivse poole pealt: hakkasin kasutama ja õppima Komodo Edit nime kandvat töövahendit oma HTML-i ja javaScript spagettide töötlemiseks. Praeguseks olen väga rahul,
sest see pole mind veel suutnud tõsiselt välja vihastada.
Selgus, et teen väga palju elementaarseid näpukaid: näide - ei leia üles, et oled kirjutanud ID Id asemel ühes kohas. Täielikult on puudu HTML-i kujundamise põhitõed - ainult elementide põgusast uurimisest ei piisa, kui tahaksid, et lehekülg näeks mingil kindlal viisil välja ja see "kujundus" ei lendaks kus kurat mingi lihtsa täienduse mõjul. Praegu jätsin selle HTML käki teistele hoiatuseks just selliseks, nagu ta on, kui tekib aeg css ja html-ga tegelemiseks, siis on pärast hea vaadata, mis lollustega ollakse kunagi maha saanud. css -ga tegelemise jätsin üldse praegu kõrvale.
On selgunud kurb tõsiasi - ei saa kuidagi enam läbi ilma abipakettideta - nii tuleks plaani võtta jQuery. Mõnede asjade tegemine algses javaScriptis võrdub assembleris programmeerimisega. Näide: vormid - jätsin ära praegu HTML vormide dünaamilise tegemise, sest originaalne javaScript ja HTML spetsifikatsioon on nii algeline, et see nikerdamine ilma korralike lisafunktsioonideta ei tasu ära, oleks vaja need kas ise valmis vorpida, või mõne targema poolt loodut kasutada. Näide - raadionupud ja märkeruudud (checkbox).
ja nii edasi ja nii edasi ... Kes siia varem juhtunud, kui 18.09.2014, siis pasteerigu kood uuesti ümber, see on veidi uuenenud.

Trips, Traps ja Trull: igihaljas mäng










Koodispagett



Mitmesektsiooniline HTML


Selle HTML asjanduse kommentaariks lisaks vaid, et ärge teie nii tehke. Tühjad read seal sees räägivad iseenda eest. Praegu jääb järgmissesse versiooni need asjad seal õigeks panna.

JavaScript kood


Kuidas ikkagi süüa spagetti?


I Igav algus

Nagu kõikjal mujalgi, on koodi alustamine kõige raskem -
Kõige õndsam hetk on kindlasti kavandamine, kastide joonistamine. Õnnis on aga see inimene, kes oma kavandatut ei pea ellu viima!
Enamik suuri projekte aga takerdub pisiasjade taha.
Jumal on peidus pisiasjades!

Kui selle mängu koodi uurida, siis võiks selle jagada 3. osaks:
I osa kuni funktsioonini syndmusHaldur (rida 257) tegeleb just täpselt selliste pisiasjadega, nagu konstandid (mis võivad ühel hetkel muunduda muutujateks), andmestruktuurid (objektid mang ja mangija1, mangija2), HTML elemendid. Neid elemente on vaja täita vajaliku sisuga või koguni tekitada või kaotada(teatenupud). HTML elementidest on HTML lehekülje peale ette ära defineeritud TTTA (peab sisaldama kanvaadest moodustatud mängulauda, selle kõrval paiknev TTTB (kavandatud kui programmi teadetetahvel, kuhu ilmuvad ja kaovad teated) ja TTTC (mis võiks sisaldada menüünuppe). Täiesti oleks võimalik ka variant, kus HTML ise sisaldaks vaid ühe elemendi, mille sisse programm dünaamiliselt tekitab elemendid TTTA, TTTB, TTTC.
Juurelement on kindlasti vajalik, selle võib istutada näiteks blogisse (nagu siin on tehtud). Kuna HTML elementidega tegelemine kindlasti on javaScripti üks põhitegevusi, siis tuleks kindlasti kõiki neid tegevusi võimaluse korral standardiseerida, tekitada nendest kõige põhilisematest mingi funktsioonide teek. Kindlasti tasuks netist üles korjata ka mõni mõistlik lisapakett, tn. jQuery, millega kõik kokku tuleks integreerida. Praeguses protofaasis aga tasuks mõelda, millistest funktsioonidest võiks saada tulevase funktsiooniteegi põhitegijad. Näiteks teadete edastamine kasutajatele või lihtsate asjade küsimine (mänguri nimi, mõni raadionupp või checkbox, dünaamiline HTML elementide loomine ja täitmine (nupud, tekstikastid)... Mingid alged on siin olemas, aga nad ei ole enamasti küllalt üldised (tunnus - looKanvaa näiteks ei sisalda parameetrina funktsiooni, mis käivitatakse hiirega klikkimisel, see on sinna "sisse kõrvetatud" (hardcoded on ingliskeelne termin), kanvaa id asemel on mingi üsna jabur number üldises kontekstis (aga ma ei hakanud seda lappima).
Selgitusi selline asi enamasti ei vaja, vajaks vaid selgitus, MIKS midagi nii on tehtud. Miks näiteks mang.sisu lubatud koodideks on -2,0,1?
Põhjus on proosaline mänguseisu analüüsi lihtsustamise uitmõte - selliste koodide korral saaks anda hinnaguid rea seisule, leides lihtsalt rea elementide summa, "kokkupõrkeid" tuleks veidi vähem, kui -1,0,1 kodeeringu korral.
Päris kindlasti aga on valik, kirjutada juba tühjale ruudule mingi sisu, parem, kui jätta kõik undefined seisu. Valikule, kus looKanvaa parameetriks on mingi jabur number loogilisema kanvaa id asemel, aga ei ole mingit õigustust, v.a. see, et nii on juba eelmises versioonis (ja päris alguses) olnud. Praegune õigustus on umbes selline - uues ja täiuslikus versioonis on meil vaid ÜKS KANVAA. Üsna sageli on funktsioonide teke seotud juhusega - mingilt hetkel avastasin, et päris mängu alguse ja esimese käigu vahelise olukorra vahe on vaid teadetetahvlil ilutsev TTT "ajalugu". Siin võiks parem plokkskeemide joonimise oskus juba varem kaasa aidata...

II Sündmuste haldamine

Tundub, et selleks, et javaScripti paremini mõista, peab uurima ka seda, kuidas javaScript toimib, mitte ainult keele konstruktsioone. Üheks võtmemõisteks siin on sündmus.
Sündmustele reageerimine on veebilehe elu. Veebiserveril on see tõenäoliselt veel paremini mõistetav, aga ka kliendipoolsete skriptide loogika lähtub sündmustest.
Kui lehekülg on kasutajale nähtavaks tehtud, HTML elemendid on loodud, ja skriptid oma algsed funktsioonid täitnud, algab lehekülje elus asünkroonne faas. Enne seda toimunut nimetatakse millegipärast sünkroonseks faasiks. Tegelikult toimib lehekülje alglaadimisel lihtne, vana, järjestikune loogika. Täpselt selles järjekorras, nagu elemendid ette tulevad, nad täidetakse. Järgnevat faasi silmas pidades võib teadlik programmeerija juba lisada lauseid ja muuta mõnede protsesside toimumise aegu (näide: kanvaade joonistamine TTT programmis, kus joonistamise alguse tingimuseks on lehekülje laadimise lõpetus), kui seda ei tehta, toimub kõik järjest, lineaarselt.
Asünkroonses faasis ei saa kuidagi ette teada sündmuste järjekorda. Kasutajale ei saa ette kirjutada, millal ja mis järjekorras ta leheküljel klikib, ja kui ka saaks, oleks see lisavaev, mis pigem peletaks inimesed veebileheküljelt kiiresti minema. Lausa võimatu on see aga veebiserveril, kus korraga tuleb tegelda mitmete kasutajatega, lehekülgede elementide kokkukorjamisega ja loomisega mitmesugustest keskkondadest jpm....
Programmeerimiskeskkonnal oleks targem leppida elu loogikaga ja loobuda inimestele oma loogika pealesurumisest. See tundub olema sündmuspõhise programmeerimise - event driven programming - peamine juhtidee. JavaScript on aga sündmuspõhise programmeerimise ideede uurimiseks väga kohane vahend, sest veebilehekülgede sündmustele reageerimiseks see keel ongi loodud.
Kui veebileheküljega seotud skriptidel kasvõi põgusalt on tööd ka asünkroonses faasis, pärast lehekülje alglaadimist, siis ei pea selle faasi jaoks eraldi plokkskeemi leiutama. JavaScripti toimimise põhiline plokkskeemi koostöös sirvikuga on SÜNDMUSTE TSÜKKEL, EVENT LOOP.













Selgitaksin ja tõlgiksin siin skeemil esitatud mõisteid, alustame krüptilisest käsust paremal - Execute callbacks - täida tagasikutsed.
btw, nagu alati olen hädas maakeelse terminoloogiaga.
Sisuliselt tuleb võtta sündmuste järjekorrast järgmine töötlemist vajav sündmus ja toimida sündmusega nii, nagu näeb ette sündmusega seotud sündmushaldur, mitte kõige loogilisema nimega tagasikutse - callback . Trips Traps Trullis on töötlemist vajavateks sündmusteks klikid 9 kanvaal ning kasutajapoolsed valikud - "Mängi arvutiga", 2 mängurit või Arvuti-arvuti. LISAKS sellele loob TTT mäng veel 2 sorti sündmusi, mis kasutaja jaoks paistavad välja kui "Arvuti mõtlemisaeg sai ümber" (tegelikult kutsutakse sündmustetöötleja tagasi teatud viivise järel, mille järel käik sooritatakse sisuliselt momentaalselt) ning "Mängu tulemusest teatamine" (siin edastatakse teade ja seejärel tagasikutse tagajärjel teade kaob tahvlilt ja mäng läheb algseisu).
Kuid tagasi sündmustsükli juurde! On oluline mõista, et sündmustsüklis saab arvuti korraga töödelda vaid ÜHTE sündmust. Küsimus ei ole mitte selles, et ei oleks võimalik panna arvuti töötlema ka mitut sündmust paralleelselt, vaid selles, et sellise loogika järgselt on programmide kirjutamine lihtsam.
Uued sündmused peavad ootama järjekorras, seni, kui sündmustetöötleja või "tagasikutse" vabaneb. See tähendab ka seda, et sündmusetöötluse programmid ei tohi väga pikalt arvuti aega raisata. Kui aga mingi asi tõesti nõuab mahukaid arvutusi, kutsutakse välja töötaja - worker . Töötajaks nimetatakse selles mudelis tagaplaanil käivituvat tegevust, mis võib rahulikult toimida nii kaua, kui kulub ja mis oma töö tulemustest annab javaScriptile teada vastava sõnumi abiga. See jõuab muidugi ka uue sündmusena sündmuste järjekorda.
Nii et ei saa öelda, et javaScript ei toeta paralleelsust. Kuid sündmustsükkel ise toimib ühe lõimena (thread), mis aga tagaplaanil väga hästi saab käivitada teisi protsesse, kui see on vajalik.
Sündmustsükli skeemi ülejäänud osa ei ole minu arusaamise järgi täpne. Seal peaks olema lihtsalt protsessi lõpetamine ning kontroll, kas sündmuste järjekorda on lisandunud uusi ootajaid. Kui ei ole, jääb põhitsükkel ise ootele. Mingit muud koodi ei tohiks seal pärast sünkroonse faasi läbimist lihtsalt olla.

Protsessid ja lõimed

Hoolimata ülesande lihtsusest jõudsime ikkagi paralleelse programmeerimise mõistete juurde. Mõni aeg tagasi oleks see ehk olnud eksootika, praegu aga ei ole võimalik kirjutada isegi kõige lihtsamat koodi, omamata vähemalt elementaarset ettekujutust, mil viisil arvuti võiks mitme ülesandega üheaegselt hakkama saada.
Sellise arengu peamine põhjus on riistvara: enam ei ole võimalik saada enesele arvutit, kus ühe protsessori sees ei oleks vähemalt 2. tuuma, sisuliselt protsessorit. Selle loo kirjutamise ajal on laialt käigus Inteli protsessorid i3,i5 ja i7. i3 natuke juba ajalooline, i5 keskmine ja kõige kasutatava ja i7 ülemise otsa protsessor. i3 omab 2 tuuma, i5 enamasti 4, i7 4 tuuma. Iga tuum omakorda on võimaline käivitama paralleelselt alates i5-st vähemalt 2. lõime, s.t. i7 protsessoriga tavaline lauaarvuti saab füüsiliselt täiesti paralleelselt käima panna 8 lõime. Pilves (cloud) toimuva korral aga on üsna tõenäoline, et iga kodanik võib seal vajadusel käima panna TUHANDEID lõimesid sisuliselt paralleelselt. Võimaluse annab selleks suure arvuti aja- ja ressursijaotussüsteem. Isegi kui kõik planeedi elanikud hakkaksid iga päev töötama pilves, kasutavad nad enemasti, 99.999% ajast väga vähe suure arvuti võimsusest. See võimaldab vajadusel, kui TÕESTI on vaja, ühe kasutaja kätte anda päris suure võimsuse. Niisiis oleme jõudnud olukorrani, kus peaegu iga kodaniku ees on arvuti, millel on sportauto võimsus - aga mis sellest hoolimata toimib sageli kui tigu. Operatsioonisüsteemid on aga juba ammu ammu jõudnud tasemele, kus isegi ühetuumalise protsessori abiga on võimalik virtuaalselt käima panna kümneid protsesse ja lõimesid. Selle näilisuse tagab kaval ajajaotussüsteem - protsessid saavad mingi kindla ajaühiku sekundist oma käsutusse, need omakorda jagunevad protsesside all toimivate lõimede vahel. Ja see kõik jaotatakse toimivaks füüsiliste tuumade vahel. Enamasti on paralleelsed toimingud sõltumatud - minu arvutis näiteks praegu on lahti (ja enamasti tegevusetus staadiumis) blogi editeerimine, Word, kuhu korjan linke, ning Komodo Edit. Need kõik on omaette protsessid. Komodo editis kaks lahtiolevat faili tõenäoliselt toimivad kui eraldiseisvad lõimed (ma ei ole küll kindel, aga nii võiks seda programeerida). Arvuti ise on aga vana XP, lihtsa editeerimise ja harjutamise jaoks ei ole praegu rohkemat vaja. Kõige paremini kapseldatud ja paralleelsete toimingute seas kõrgeimal kohal on PROTSESSID ehk TEGUMID. (ei hakka lahkama võimalikke nüansierinevusi nende vahel, kui neid on). Windows-s saab toimuvaid tegumeid jälgid Task Manager abiga ja selgub, et isegi kõike tagasihoidlikum vana rüsa on võimeline käigus hoidma arutult palju erinevaid tegumeid või protsesse. Enamasti tavaprogrammeerijal protsesside haldamisega palju pistmist ei tule - praktiliselt on ette tulnud vajadus mõni asi käivitada või rajalt maha võtta. Süsteemprogrammeerimine on teine lugu, see tegeleb otse operatsioonisüsteemiga. Lõimed on alamprotsessid ühe protsessi sees, mis on võimelised toimima paralleelselt. Nende programmeerimine on praegu asi, millest iga professionaalne programeerija kindlasti ei saa üle ega ümber. Otse võimaldab lõimedega tegelemist Java ja klassikaline C++. JavaScript saab parallelselt kulgevaid tegevus programmeerida kaudselt.
Igal asjal on aga oma hind. Java programmerijatel tuleb oma koodi käimaajamiseks ja käigus hoidmiseks tegelda palju rohkemate probleemidega. Sündmustsükli lihtsus võimaldab JavaScriptis kirjutada palju paremini silutud ja veavabamat koodi. Nii et "eelis" oma koodi optimiseerida ei pruugi olla eelis. Donald Knuth on öelnud optimiseerimise kohta kuldsed sõnad: "ENNEAEGNE optimiseerimine on kõige kurja juur...". Ja Murphy RAAMAT ütleb lihtsalt ja selgelt: "Ärge VEEL optimiseerige". Kuigi paralleelne programmeerimine kindlasti ei ole raketiteadus, on ta siiski märgatavalt raskem selgeks saada. Järelikult puht-pragmaatiliselt on ka sündmustsüklil põhinev javaScript parem, sest isegi kõige kogenematum algaja on siin võimeline mingid toimivad koodiread kirja saama. (C++-s lõppeks asi tõenäoliselt vaikusega). Hind sellele on muidugi tohutu hulk spagettikoodi, mida erineva tasemega inimesed erinevatel aegadel on kirjutanud. Aga kogu see kräpp toimib mingil imelikul moel, k.a. praegune TTT III mäng. Soovitan tutvuda järgmise presentatsioniga: Miks lõimed on halb idee? (John Ousterhout) Põhilise mõtte sellest annab edasi 5. slaid sellest ajaloolisest presentatsioonist:

Lihtsalt ja selgelt öeldes on selliseid inimesi, kes oleksid võimelised korrektselt lõimi programmeerima, väga vähe programmeerijate üldarvust.

setTimeout ja setInterval

Peale addEventListener meetodi saab on veel meetodeid tagasikutsefunktsioonide sättimiseks. Kui on vaja mingi funktsioon käivitada mingi ajalise viitega, siis sobib selleks
setTimeout(funktsioon, viide millisekundites) setTimeout täitmise järel on viidatud funktsioon pandud sündmuste järjekorda ja mitte varem kui millisekundites antud aja järel proovitakse nimetatud funktsioon käivitada. Loomulikult mingit pausi ei tule, süsteem jätkab kohe järgmise käsuga pärast setTimeout operatsiooni sooritamist ning viitega käivitamine saab teoks ka alles siis, kui süsteem naaseb sündmustsükli põhiolekusse, kus ta saab kontrollida järgmist sündmust järjekorras... Mingi asja korduvaks käivitamiseks, näiteks mõnes mängus palli liigutamiseks üle mänguvälja, on vaja kasutada f-ni
setInterval(käivitatav fn,intervall käivituste vahel) Samad asjad saab ka käivituste järjekorrast kustutada, setTimeout vastandoperatsiooniks on clearTimeout(t) ja setInterval kustutamiseks sobib clearInterval(t). Et seda teha saaks, peab vastava setTimeout või setInterval ID mingisse muutujasse salvestama, näiteks var t=setTimeout(f-n,millisekundid);
Märkus: Pärast setTimeout-ga paigaldatud tagasikutse toimumist seda muidugi enam kustutada ei ole vaja...

Sündmushaldur ja dispetser

Tuleks tagasi Maa peale, koodi juurde.
Igale käitlemist vajavale elemendile mängus on sätitud tagasikutse kujul syndmushaldur(i), kus i on mingi number. Kuigi võiks üldse number i-st loobuda ja kasutada sündmuse enese omadust event.target, on päris mugav juba kohe kasutada enesele meeldivat tähistusviisi (numbrid).
Numbrid 1-9 viitavad mängulaua ruutudele, 0,-1 ja -2 on praegu menüünupud.
0 tähistab mängu arvutiga, -1 kahe mängija duelli, -2 arvutite võimuvõitlust.
Programm kasutab kahte lippu sündmuste töötluse juhtimiseks: mang.kanvaaKlikkOn - kas mängulaual klikkimine mingi tulemiga on võimalik, ning mang.nupuKlikkOn - kas menüüvalik on võimalik. Viimane võiks olla kogu aeg võimalik, kuid kodeerimise mugavuse mõttes ei tahtnud ma menüüvalikul lasta tulemuslikult klikkida, kui mingi ajaline viide on jõus (tagasikutse sätitud funktsiooniga setTimeout())- siis ei pea tegelema selle viitega seotud tagasikutse kustutamisega.
Kui lipp on false, tagastab ka syndmusHaldur() false (see on puht stilistiline, võib ka mitte midagi tagastada) ja süsteem läheb oma põhitsüklisse tagasi, jäädes uute väljakutsete ootele.
Kui mängulaua ruut on juba märgitud kas X-i või 0-iga, siis syndmushaldur() samuti ei analüüsi sündmust edasi. Edasine töö toimub "järgmise taseme" funktsioonis dispetser(el).
dispetser() vastutada on ka uute klikkide taaslubamine. Kui seda ei tehta, jookseb programm kinni, kõik hiireklikid on igasuguse mõjuta.
dispetser(el) argument el võib omada veel 2 täiendavat väärtust - 10 ja 11. Need on programmi enese poolt säetud tagasikutsed.
10 tähistab olukorda, mis tekib arvuti käigul olles. Pärast käigu alustamist arvuti poolt on vaja emuleerida mõtlemispausi. Selleks kasutab dispetser() käsku setTimeout(dispetser(10),TTTPAUS)); 11 on vajalik mängu lõpptulemusest teatamise pausi tekitamiseks, mängu lõppemise kood on samuti jagunenud pooleks - kõigepealt teatatakse tulemusest, siis tehakse paus, sätitakse tagasikutse, tagasikutse 11 järel aga läheb mäng tagasi algseisu.
Siin paistab kohe välja sündmuspõhise programmeerimise põhihäda - sündmused dikteerivad kirjutatava programmi koodi, loogiliselt ühes kohas olev kood tuleb jagada kaheks (või enamaks) jupiks. Sellest oleks meid säästnud lihtne funktsioon sleep(), aga sündmustsüklil põhinevatel süsteemidel ei ole sellist asja olemas. Parem seda mitte taga nutta ja kohaneda asünkroonse programmeerimise võludega.
MÄNGU loogika.
Mängu alustamisel initsialiseeritakse objekt mang, sätitakse vastavalt menüüvalikule mangija1 ja mangija2 väärtused, puhastatakse kanvaad, puhastatakse teadetetahvel. Kui mängijaks on arvuti, siis tema tase määratakse juhuslikult (kas 0 või 1). Seda teevad käsud initMang(false) ja initMangijad(el). Seejärel algab mäng. Iga käigu alguses toimetab funktsioon alustaKaik() - teatab, kes käigul.
ad hoc -na lisandus siia viimasel minutil mõte kommenteerida mängu. See lisandas siia segadusttekitavat spagettikoodi - ka mängijate omavahelises mängus küsitakse seisu hinnangut ja tulemusena väljastatakse kommentaar. Seisu hinnang küsitakse arvutilt alati tasemega 1, selleks on funktsioon kysiArvutiRek().
Pärast käigu alustusteadet läheb otsustamine üle mängijale või emuleerib programm arvuti mõttepausi. Kui mängija on teinud otsustuse või arvuti mõttepaus tiksus täis, peab selle tulemiks olema mingi valik el=1-9 või el=10 (arvuti mõttepaus). Arvuti puhul nüüd küsitakse järgmist käiku, kasutades vastavalt mõtlemistasemele f-ni kysiArvuti() (tase 0) või kysiArvutiRek() (tase 1, rekursiivne MinMax algoritm). Seejärel genereeritakse sündmus el 1-9, taas kutsudes välja dispeter(el) (Ka see on sisuliselt tagasikutse, callback). Käigu sooritamine tähendab käigu kirjutamist partiisse, mang.sisu vastava elemendi tähistamist 0- või 1-ga, kanvaale X või 0 joonestamist. Seda teeb sooritaKaik(). Pärast käigu sooritust oleks vaja teada tulemust funktsiooni kasVoitis() abil. See f-n analüüsib mängulauda ja vastavalt argumendina antud sümbolile kontrollib, kas võitis 0 (joonistatud kui ring) või 1 (X). Kuna võita saab vaid see, kes käigul, siis parameetriks on mang.kaik%2. Kui käigu number on paaritu, siis on käigul 1. mängija (sümbol 1), paarisnumbri korral on käigul 2. mängija (sümbol 0).
kasViikRek(symbol) vastab küsimusele, kas positsioon on viigis või ei ole. Rek tähista seda, et kasutatakse rekursiivset analüüsi.
kasViik() funktsiooni kasutamine on ka võimalik. Praegu pole see kasutusel. See on põlve otsas valmis treitud viigi analüüs, mis ei tööta näiteks siis, kui jäänud on 2 tühja ruutu. Aga selle lõpuks kõrvaldatud funktsiooni tõttu sai mängulaua kodeeringuks -2 (tühi), 0, 1, mitte -1,0,1, nagu oleks loogiline.
Kui nüüd keegi on võitnud või mäng läheb viiki, tuleb ekraanile teade selle kohta Arvuti genereerib vastava pausi ja tagasikutse dispetser(11). Tagasikutse dispetser(11) lõpetab mängu ja paneb süsteemi algolekusse kutsega initMang(true). true tähistab seda, et on tõesti algus, mitte mängu algus ja lisab teksti TTT "ajaloost". Sellel tekstil ei ole tegelikkusega mingit muud pistmist kui see, et minu auto number on 846. Märgime el===11 puhul koodis ilutsevat spagettielementi, mis kustutab kommentaatori hinnangu mängupositsiooni kohta. See rida peaks sealt olema puudu ja selle kohta tehtud eraldi funktsioon.
Sellega on programmi põhiloogika käsitletud. Loodetavasti järgmistest versioonides asendab pikka juttu normaalne plokkskeem (praegu jäi see ära tehnilistel põhjustel).
Alles nüüd, kusagil reast 420 kuni lõpuni (630) saab tegelda mängu sisulise poolega. Siin on lõpuks avanenud meeldiv võimalus tutvustada REKURSIOONI ja MINMAX algoritmi.

Rekursioon -vt. Sekeldaja: rekursioon

Hoolimata ilusast ja keerulisest sõnast ei ole rekursiooni kasutamine mingi raketiteadus. See on peaaegu sama, mis kordamine, ainult et puudub väline tsükkel, mis muidu peaks kordamist või "korrutamist" (tähenduses tüütult korrata) juhtima. Kordamise ja korrutamise juhtimiseks on aga rekursiivse meetodil käsutuses ülivõimas vahend - pinu. Pinu või magasin või stack on koht, kuhu andmed saab panna nii, et viimasena pandud andmed saab esimesena kätte. Küttepuud, asetatuna pinusse, käituvad just nii ja kena eestikeelne oskussõna selgitab seetõttu kõik, ilma et peaks teoretiseerima. Funktsioonid toimivad samamoodi. Kui programmis on mingi funktsioon, mis tuleb täita, pannakse mäluaadress, kuhu programm peab tagasipöörduma koos vajalike andmetega pinusse. Funktsioon teeb oma tööd, ja näiteks kutsub iseenda välja. Jälle pannakse andmed pinusse ja minnakse uuesti funktsiooni täitma. Kui see asi lõpuks lõpeb, siis viimane väljakutsutud f-n tagastub, seejärel tagastub eelviimane etc..., selle käigus võetakse kõik andmed pinust täpselt õiges järjekorras jälle välja ja programmi täitmise lõpuks või mõnes lõpmatus ootavas tsüklis on see pinu jälle tühi. Operatsioonisüsteemi käitavatest pinudest ja peensustest ma ei räägi, ma räägin teie programmist, millele on eraldatud oma "pinu" just selliste asjade tegemiseks, k.a. rekursioon.
Mõni õpikunäide rekursioonist:
FAKTORIAAL (ad nauseam toodud igas rekursiooni käsitlevas jutus alustusena) Esimeses näiteks on arvutiga "koosmõtlemine" lihtsam. i algab 1-st ja jõuab n-ni ja iga kord korrutatakse i-ga f-i ja tagastatakse f. Teises näites algab tegelik rehkendus alles siis, kui jõutakse kutseni n=1, seejärel tagastatakse see väärtus, järgmine tagastus tagastab 2*1, ülejärgmine 3*2*1 ja nii edasi, kuni saadaksegi faktoriaali õige väärtus. Kuigi rekursiivne meetod esialgu tundub veider, on see lihtsam, ei ole abimuutujaid, ometi kõik arvutub ilusti välja. Väga väikse nuputamisega, asendades "*" märgi "+" märgiga ja tsüklivariandi puhul f algväärtuse 0-ga, saame arvude summa arvutamise 2 varianti. Aga oh häda! Summat kuni 100000-ni rekursiivne variant ei arvuta (minu arvutis lihtsalt ei väljastata midagi).
Põhjus on lihtne - pinu läheb liiga sügavale. Mingiks mõtteliseks piiriks loetakse 10000 korda iseenese väljakutsumist, rohkem javaScript hästi ei võimalda. Saadakse ilus viga nimetusega "stack overflow". Seda tasuks igal progemisega tegelejal meeles pidada, sellies kohaga saidilt saab sageli midagi kasulikku teada, kui mõni asi programmis käitub veidralt. Vigu teevad ka teised surelikud ja nende vigadest on kasulik õppida - "stackoverflow.com" just selliseks suhtluseks ongi mõeldud.
"Kuidas siis puhtalt "funktsionaalsed" keeled sellega hakkama saavad, kus peale funktsioonide väljakutsumise mingeid muid meetodeid töö tegemiseks polegi?" küsib natukene teadvam lugeja.
Selgub et nendes keeltes sellised banaalsed rekursioonid nimelt jälle tehakse tsükliteks tagasi, seda nimetatakse SABAKUTSE OPTIMEERIMISEKS (Tail Call Optimization).
Et funktsionaalne keel siiski toimiks, on seda hästi vaja, 10000 kordsest rekursiooni sügavusest kindlasti ei piisa alati. Samas on need tavaliselt sisuliselt üsna lihtsad ülesanded ja neid saab kerge vaevaga tsükliteks tagasi teha. JavaScriptis on selleks vaja veel oodata versiooni "Harmony" realiseerumist (kohe kohe teoks saamas).
Ma kaldun aga faktoriaali põhjal arvama, et just sellisel mõttetul banaalsuse ületirazheerimisel on oma osa selles, et tänapäevani peetakse rekursiooni vaid kavalaks õpikumeetodiks, millega tegelikus elus midagi peale hakata pole. TTT mängu näide minul küll näitab vastupidist, nagu üldse iga kõige lihtsamat sorti mäng seda teeks, ilma rekursioonita oleks arvuti väga abitu mängur. Veidi kavalam näide, kus tõesti rekursiivne variant on tükk maad parem, on Fibanocci arvude rehkendamine. (vt. ka harjutusi, seal saab kõike ka katsetada) Rekursiivset varianti on tunduvalt lihtsam üldistada, igat sorti selliste jadade rehkendused võiksid käia kõik sama malli kohaselt.
Fibanocci arvud Sirvisin seda pinuülevoolamise kohta (stackoverflow) ja ei leidnud ka paremat "tavalise" progemise meetodit. Siin on rekursiivse meetodi ülekaal lausa masendav. Üks rida ja asi on arvutatud!
Wilhelm Ackermann on õnnistanud maailma ja kompuutriala õpilasi järgmise rekursiivse funktsiooniga: ack(0,x), ack(1,x) ja ack(2,x) lähevad kõik ludinal. Milles probleem? ack(3,x) arvutamine aga millegipärast läheb järjest uimasemaks ja uimasemaks, Minu Xp pidas vastu väärtuseni ack(3,10). Ärge üldse proovigegi suuremaid m väärtusi. Keda huvitab, lugegu WIKIST edasi, aga funktsioon on oluline seetõttu, et mitte ühegi valemiga ei ole võimalik seda programmina kirja panna mõnel muul moel, kui rekursioon. Eranditeks on ack(0,x)...ack(3,x) ja tinglikult ka ack(4,x) arvutamine, kus saab tinglikult kirja panna funktsiooni valemi... Kõrgematel m väärtustel seda enam teha ei saa. Kahjuks siin ka igasuguse arvuti võimekus lõpeb. Nii on ackermanni funktsioon alates m=5 ja rohkem üks täiesti arvutamatu asi.

MINMAX ehk koodi "süda"

Jätkame jonni selle TTT-ga ja javaScriptiga.
Intuitiivselt tunduks, et mingid "lihtsad" variandid (kontrollime, kas keegi ähvardab võita, ehk on kohe kaitsta vaja, kas saab kahvlit panna, kas saab mõne olulise välja "vallutada" oleks palju kergem programmeerida, kui kogu mängu banaalne lõpuni läbi vaatamine. Tegelikult on otse vastupidi. Ei ole lihtsamat asja, kui rekursiivsete korralduste jagamine. Enamik probleeme ei lahendu sel moel, sest see pinu, mis rekursiivseid funktsioonikutseid meeles peab, saab täis ja ekraanil ilutseb tuim stack overflow teatis. Õnneks TTT selline probleem ei ole ja lahendub banaalse MINMAX meetodiga. Enne selle juurde jõudmist aga veel banaalsem viigi probleemi uurimine. Algoritmi sisu: ehk on juba kellelgi võiduseis, siis viik ei ole. Kui kellelgi võiduseisu ei ole ja käikude arv täis siis on viik kui käike liiga vähe tehtud (<4), siis ka viiki ei ole. Ja siis banaalsuste banaalsus: otsime üles kõik vabad väljad, täidame ühe ära ja küsime jälle, äkki on viik. Kui sattusime mingi võiduseisu peale, siis ei olnud viik, s.t. mingi viisil saavad mängijad tekitada olukorra, kus üks võidab. Muidu kontrolime, äkki on viik teiste positsioonide puhul ka ja alles siis, kui kõik variandid viivad viigini, ongi viik. Kõik mured lahendab jälle rekursioon ja teadmine, et ükskord tuleb mingi võit või viik niikuinii ette, hiljemalt käigul n===9 See koodijupp muuseas ei vastuta teate "igav viik" eest, see on hoopis teine asi - õige mängu puhul läheb mäng viiki… Ja nüüd see "keerukas" MINMAX. kysiArvutiRek(sisu,kaiknr) tagastab kahekomponendilise massiivi.
esimene element on parim käik, mida arvuti soovitab, teine sellele käigule antud hinnang. (võib olla ka hävitav, siis parimat käiku ei olegi, kõik variandid kaotavad).
Kõigepealt vaatame ära terminaalsed või lõppseisud. Sel juhul käigu nr-ks, mida tagastada, tuleb 0, mäng on tehtud, hinnang kas 1 (võit), 0 (viik) või -1 (kaotus). Nüüd aga algoritmi süda: küsime hinnangut kõikide käikude kohta, mida saab teha, ainult et miinusmärgiga, sest siis analüüsitakse juba vastase võimalusi. See ongi minmax nimetuse põhjus, sest teise, vastase poole analüüsist võetakse miinimum maksimumi asemel, aga selle nipi teeb programmis ära hinnagu võtmine miinusmärgiga. Ma isegi ei oska öelda, kas mitte alati ei ole võimalik selle asemel kasutada miinusmärgiga hinnangut, vist ei ole. Nendest hinnangutest valime välja maksimaalsed ja võimalike käikude keskelt valime välja juhusliku võidule või vähemalt viigile viiva käigu. Aga kui midagi teha ei ole, teeme ühe juhusliku käigu lihtsalt (kõik variandid kaotavad). Huvitava nüansina selle algoritmi puhul hakkas arvuti oma võidu vormistamist "edasi lükkama" VÕite seda mõne mängu puhul arvutiga proovida - mõnikord arvab arvuti, et võidu vormistamisega võib viivitada ja ta ei lõpeta mängu kohe kolmanda risti panekuga ritta.