Page 1 of 1

Arkistojen kätköistä: Type-tutoriaali

Posted: Fri May 08, 2009 9:05 pm
by ezbe
Edit 19.10.2011: Korjattu kuvalinkit

Disclaimer: Voi sisältää asiavirheitä, typoja ja virheellisiä oletuksia. Alkuperäinen tuto on vuodelta 2006, joten osa linkeistä on vanhentunut, eikä näin ollen toimi (Vihonviimeinen edit: Turhat/vanhentuneet tagit pitäisi viimein olla korjattu, samoin vanhat linkit poistettu

Osa 1. Perusteet: instanssien luonti, viitemuuttujat ja listan läpikäynti
"Type? Siis mikä v***n type?!"
Type (tyyppi) on määritelmä, jossa kootaan yhteen jonkin esineen, asian, olennon tms. tarvitsemat muuttujat yhdeksi "paketiksi", ja josta voidaan sitten luoda ko. tyypin esiintymiä (instansseja). Huonoja vertauskuvia käyttäen, type-määritelmät voidaan käsittää yleismallisina "piirrustuksina" tai "suunnitelmina", joista sitten luodaan varsinaisia todellisia esineitä tai asioita. (Tämä on muuten oiva aasinsilta olio-ohjelmointiin, jos sisäistät edellisen huonon selityksen, olio-ohjelmointi lienee sinulle suht helppoa)

Code: Select all

//Auton määritelmä
Type Auto
    Field Merkki$            //Auton merkki (Esim. "Audi", "BMW")
    Field Mallinimi$        //Auton mallin nimi (Esim. "A6", "Roadster")
    Field Vuosimalli%        //Auton vuosimalli (Esim. 2006)
    Field Kulutus#            //Polttoainekulutus per 100km
End Type
Typet voivat sisältää ainoastaan perustyyppisiä muuttujia (kokonaisluku, liukuluku, merkkijono), eivät taulukoita tai viitteitä toisiin tyyppeihin. Tähänkin on kuitenkin olemassa kikkoja, joita esittelen tutoriaalin toisessa osassa.

Sivuhuomautuksena, jos joku ei tätä vielä tiedä, muuttujan nimen perässä oleva merkki (%, # tai $) kertoo muuttujan tyypin (jos muuttujan nimen perässä ei ole mitään merkkiä, se on oletuksena kokonaisluku):
% = kokonaislukumuuttuja (Esim. 1, 2006, -15684)
# = liuku- eli desimaaliluku (Esim. 1.0, 200.38, 3.1415)
$ = merkkijono (Esim. "Merkkijono", "tekstiä", "blah", huomaa että merkkijono sijoitetaan lainausmerkkien sisään)

Jos muuttujan tyypiksi halutaan joku muu kuin kokonaisluku, pitää määrittelyssä käyttää joko # tai $ -merkkiä. Itselläni on tapana mättää merkit aina myös kokonaislukuihin, mutta tämä tosiaan ei ole välttämätöntä. Määrittelyn jälkeen merkin käyttö ei ole enää pakollista muuttujaa myöhemmin käytettäessä.

Yllä oleva type määrittelee kentissään (field) siis "geneerisen auton" ominaisuuksia (attribuutteja). Kyseiset ominaisuudet esitetään erilaisina muuttujina, kuten esimerkissä merkki ja mallinimi merkkijonoina, vuosimalli kokonaislukuna ja kulutus liukulukuna. Pelkkä määritelmä ei kuitenkaan vielä itsessään tee mitään, vaan siitä pitää luoda instansseja (Eli "todellisia" esiintymiä, jotka kuvaavat jotakin tiettyä autoa, eivätkä vain autoa yleensä). Tälläiselle instanssille voidaan sitten asettaa arvoja muuttuja-kenttiin, ja ne koskevat vain ja ainoastaan sitä tiettyä instanssia, jolle ne on asetettu.

Typestä voidaan luoda esiintymä yksinkertaisesti antamalla New-komennolle parametriksi halutun tyyppimääritelmän nimi:

Code: Select all

New(Auto)   //Älä kuitenkaan käytä tätä tällaisenaan, jos haluat heti asettaa instanssille
            //arvoja, vaan lue eteenpäin miten otat New:n palauttaman viitteen talteen
Mutta miten sitten muutetaan juuri luodun auton ominaisuuksia (eli muuttuja-kenttiä)? New-komento palauttaa viitteen juuri luotuun instanssiin, ja se voidaan ottaa talteen erityiseen type-instanssin "osoittimeen", josta itse käytän yleensä nimitystä "viitemuuttuja". (Ei kuitenkaan aloiteta sotaa siitä, onko se oikeasti osoitin, viite vai jokin muu, jooko? ;) )

Viitemuuttuja määritellään näin:

Code: Select all

Tunnus.Tyyppi
jossa Tunnus on viitemuuttujan nimi (muuttujanimi), jolla sitä käytetään, Tyyppi taas kertoo minkä tyyppiseen type-instanssiin viitemuuttuja osoittaa.

eli jos havainnollistetaan samaa Auto-typen mukaisesti:

Code: Select all

ViiteAutoon.Auto
Tälläinen viitemuuttuja siis viittaisi Auto-typen instanssiin, ja kyseisen instanssin muuttujakenttiin päästäisiin käsiksi tunnuksen kautta:

Code: Select all

ViiteAutoon\Vuosimalli% = 2006
Mutta miten siis saamme viitteen siihen luotuun autoon? Määritellään viitemuuttuja, ja asetetaan sen sisällöksi New-komennon palauttama viite:

Code: Select all

//Luodaan auto-määritelmän mukainen instanssi, ja tallennetaan viite viitemuuttujaan
Auto.Auto = New(Auto)

//Asetetaan juuri luodun auto-instanssin ominaisuudet
Auto\Merkki$ = "Teat" //Telajärven autotehdas http://www.saunalambusplaza.net/teat/;)
Auto\MalliNimi$ = "Wanker 4x4 ST"
Auto\Vuosimalli% = 2006
Auto\Kulutus# = 46.0

Tässä viitemuuttujan tunnuksenakin on käytetty Auto:a, joka siis on sama kuin Type-määritelmän nimi. Tämä on täysin sallittua, mutta kuten ylemmässä esimerkissä, tunnuksena voi olla mikä tahansa (ViiteAutoon.Auto). Edit: Bagard huomasi tavan, jolla tunnuksena voi käyttää type-määritelmän nimeä myös silmukoissa, tästä lisää alempana.

Nyt on siis luotu yksi tietty auto ja annettu sille arvot. Auto-instansseja on näin ollen olemassa se yksi ja ainoa.

Viitteen osoittama instanssi voidaan poistaa komennolla Delete:

Code: Select all

//Poistetaan luotu auto
Delete Auto.Auto

"Jos siis haluan luoda kaksi autoa, minun pitää luoda kaksi viitemuuttujaa, ja ottaa New:stä saadut viitteet niihin?"

EI! Tässä piilee koko type-järjestelmän nerokkuus. Yksittäinen viite voi viitata mihin tahansa edustamansa type-määritelmän mukaiseen type-instanssiin. Eli voit käyttää samaa viitettä käydessäsi kaikki olemassaolevat instanssit läpi, viite vain asetetaan osoittamaan aina toiseen instanssiin välillä. Havainnollistetaanpa:

Code: Select all

//Luodaan 10 autoa
For i = 1 To 10
    //Tallennetaan saatu viite aina samaan viitemuuttujaan
    Auto.Auto = New(Auto)
    
    Auto\Merkki$ = "Auto"
    Auto\Mallinimi$ = "Malli"
    //Asetetaan vuosimalliksi 2000 + for-silmukan indeksi i
    //Vuosimalleiksi tulee siis ensimmäiselle instanssille 2001 (2000 + 1),
    //toiselle 2002 (2000 + 2) jne., aina 2010 asti)
    Auto\Vuosimalli% = 2000 + i
    Auto\Kulutus# = 15.5   
    
Next i
Yllä oleva koodinpätkä luo 10 auto-instanssia, joilla jokaisella on omat arvot muuttuja-kentissään. Jokainen instanssi on siis erillinen esiintymänsä, ja jokaisella niillä on omat erilliset arvonsa, jotka eivät vaikuta muiden instanssien arvoihin. Tässä tapauksessa jokaisen merkiksi asetetaan "Auto", mallinimeksi "Malli" ja kulutukseksi 15.5, mutta muuttujat ovat kuitenkin erillisiä, vaikka arvot ovat samoja. Vuosimalliksi on asetettu 2000 + silmukan indeksi, joka siis kasvaa jokaisella silmukan suorituksella (oletan tässä, että for-silmukan käyttö on jo tuttua).

Viite "Auto.Auto" osoittaa silmukan jälkeen silmukan viimeisenä luotuun instanssiin. Mutta mihin ne muut sitten menivät? Kun luot uuden instanssin New-komennolla, se sijoitetaan sisäiseen linkitettyyn listaan viimeiseksi. Vaikka instansseihin ei osoittaisi yksikään viitemuuttuja, ne ovat silti olemassa listassa.

Tässä vaiheessa voi olla hyvä hetki selittää, mikä oikein on linkitetty lista. Linkitetty lista on tietorakenne, jossa jokainen alkio (tässä tapauksessa type-instanssi) sisältää viittauksen listan seuraavaan alkioon. CB:n typejen tapauksessa kyseessä on itseasiassa kaksoislinkitetty lista, joka eroaa "tavallisesta" linkitetystä listassa ainoastaan siinä, että alkiot sisältävät myös viitteen itseään edeltävään alkioon. Näin siis jokainen instanssi "tietää" itseään seuraavan ja itseään edeltävän instanssin. Selitän listan läpikäyntiä kohta tarkemmin.

Image

Kuva havainnollistanee asiaa hieman paremmin. Eli jokainen laatikko jossa on numero, on yksittäinen instanssi, ja laatikosta lähtevät nuolet osoittavat aina edelliseen ja seuraavaan instanssiin. Listan ensimmäisen instanssin "edellinen"-nuoli ja viimeisen "seuraava" nuoli osoittavat nk. "NULL":iin, joka siis käytännössä tarkoittaa, ettei listalla ole näiden instanssien tapauksissa ko. "suunnissa" mitään seuraavaa tai edellistä instanssia. NULL liittyy myös virheiden välttämiseen typejä käsiteltäessä, mutta palaan siihen hetken kuluttua.

Kuvassa on siis olemassa neljä instanssia, ja kun taas lisätään yksi, menee se neljännen perään, jolloin neljännen "seuraava" osoittaa juuri luotuun instanssiin, ja luodun instanssin "edellinen" osoittaa instanssia nro 4. Luodun instanssin "seuraava" osoittaa NULLiin, koska siitä tuli vuorostaan listan viimeinen, eikä sen perässä näin ollen ole instansseja.

Kun listan keskeltä poistetaan instanssi, CB korjaa listan viittaukset automaattisesti, eikä listan keskelle synny "tyhjiä" kohtia. Esimerkkinä jos kuvassa näkyvästä listasta poistettaisiin laatikko 2, laatikko 1:n "seuraava" -viite asetettaisiin osoittamaan laatikkoon 3, ja laatikon 3 "edellinen" -viite osoittamaan laatikkoon 1, eikä lista rikkoonnu. Tämä on yksi typejen suurimmista eduista verrattuina tavallisiin taulukoihin. Lisäksi type-instanssien kanssa ei tarvitse miettiä, kuinka suuria taulukoita tarvitaan, koska tilaa varataan aina tarpeen mukaan kun uusi instanssi luodaan (toki tietokoneen muistin määrä asettaa rajoituksia maksimimäärälle).

Image

Kuva esittää 2. alkion (laatikon) poistoa

CB:ssä on komennot myös instanssien lisäämiseen listan alkuun tai keskelle, mutta niitä käsitellään tutoriaalin toisessa osassa.


Nyt keskitytään listan läpikäyntiin. Lähdetään liikkeelle yksinkertaisimmasta, ja varmasti myös käytetyimmästä läpikäyntimetodista, eli For...Each -rakenteesta. For...Each toimii hyvin samankaltaisesti kuin tavallinen For-lausekin, mutta se on tarkoitettu erityisesti type-listojen läpikäyntiin. For...Each käy läpi jokaisen annetun tyyppimääritelmän instanssin, listan määräämässä järjestyksessä alusta loppuun.

Code: Select all

//Tulostetaan jokaisen autoinstanssin vuosimalli
For AutoViite.Auto = Each Auto
    Print AutoViite\Vuosimalli%
Next AutoViite
Koodinpätkä tulostaa jokaisen aikaisemmin luodun auton vuosimallin ruudulle. Jostain syystä For...Each -rakenne ei anna käyttää samannimistä tunnusta ja type-määritelmää (Auto.Auto), vaan tunnuksen on oltava eri. Enpä ollut tätäkään huomannut aikaisemmin (käytän yleensä pitempiä ja kuvaavia tyyppimääritelmien nimiä, ja lyhyitä tunnuksia).

EDIT: Bagard huomasi, että type-määritelmän nimi ja tunnus voivat olla samoja, jos silmukan tekee näin:

Code: Select all

For Auto.Auto = Each Auto
    Print Auto\Vuosimalli%
Next Auto.Auto
Huomaa siis, että Next:ssä ei ole käytetty pelkkää Auto:a, vaan Auto.Auto:a (tunnus.tyyppi).

Jos käydään tarkemmin läpi mitä tuossa nyt tapahtuu, niin ensimmäisenä koodin osuessa riville For AutoViite.Auto = Each Auto, AutoViite asetetaan osoittamaan listan ensimmäiseen instanssiin. Jos lista ei ole tyhjä (eli on luotu vähintään yksi instanssi), suoritus siirtyy silmukan sisälle, jossa tässä tapauksessa on vain yksi komento, Print AutoViite\Vuosimalli%, joka siis tulostaa ruudulle AutoViite -viitemuuttujan osoittaman instanssin vuosimallin. Kun suoritus saapuu riville Next AutoViite, viittausmuuttuja siirtyy osoittamaan seuraavaa instanssia listassa. Jos listassa ei ole enempää instansseja ("seuraava" -viite osoittaa NULLiin), silmukan suoritus loppuu, ja siirrytään koodin suorituksessa eteenpäin. Silmukan suoritus voidaan myös keskeyttää komennolla Exit.

HUOM! Jos silmukka on suoritettu loppuun saakka, silmukan käyttämä viitemuuttuja osoittaa lopuksi NULLiin! Jos viitteen läpi yritetään käsitellä jotain sen ollessa NULL, ohjelma kaatuu MAViin.

Jos viitemuuttuja ei osoita mihinkään instanssiin, on sen arvo NULL. Kuten ylhäällä mainitaan, jos NULL-viitteen läpi yritetään käsitellä esimerkiksi jotain instanssin kenttää, ohjelma kaatuu, koska käsiteltävää instanssia ei ole ("NULL" = Nolla, "tyhjä", vrt. sanonta "null and void" = mitätön, pätemätön, kelpaamaton). Onneksi tähänkin on ratkaisu

Code: Select all

//Tarkistetaan viite
If AutoViite = NULL Then
    Print "AutoViite ei osoita mihinkään instanssiin!"
EndIf
Eli myös viitemuuttujilla voidaan suorittaa vertailuja, vertailut voidaan tosin tehdä vain muiden samaa type-määritelmää käyttävien viitemuuttujien tai NULLin kanssa (huomaa siis että puhun viitemuuttujista, en viitteen osoittaman type-instanssin kenttien sisältämistä muuttujista, esim. "If AutoViite\VuosiMalli% = 2006 Then" on täysin kelvollinen vertailu, jos AutoViite osoittaa johonkin Auto-instanssiin). Yllä oleva koodi vertaa siis, osoittaako AutoViite NULLiin, ja jos osoittaa, tulostaa tästä ilmoituksen ruudulle. Viitteet kannattaa tarkistaa vertailulauseilla AINA kun on olemassa mahdollisuus, että viite saattaa olla NULL! Jos If...EndIf -lohkon sisäinen koodi halutaan suorittaa silloin, kun viite oikeasti osoittaa johonkin instanssiin, voidaan käyttää vertailua "If AutoViite <> NULL", tai Else-lohkoa yllä esitetyn vertailulauseen kanssa.

Vielä eräs huomautus viitteistä: viitemuuttujista ei voi tehdä CB:ssä globaaleja, ja esimerkiksi funktioon ei voi välittää suoraan viitettä tiettyyn instanssiin. Tätä varten on kuitenkin olemassa ConvertToInteger- ja ConvertToType-komennot, joiden käyttö käydään läpi tutoriaalin toisessa osassa, jossa kerrotaan miten välittää ja palauttaa viitteitä funktioista em. komentojen avulla. Jos funktiossa kuitenkin luodaan instanssi, pääsee siihen luonnollisesti käsiksi aina listan kautta.

Koska CB:llä yleensä tehdään pelejä, tyypeillä usein luodaan esimerkiksi ammus- ja pelihahmo-instansseja, joilla on objekteja tai kuvia (image). Objektin tai kuvan "handle" (kahva) on myös kokonaisluku, joten se voidaan tallettaa suoraan kokonaisluku-tyyppiseen muuttuja-kenttään, mutta ennen instanssin poistoa pitää muistaa poistaa myös sen käyttämä image tai objekti, jotta se ei jää muistiin ja aiheuta näin muistivuotoa. Lopussa oleva graafinen esimerkki käyttää type-instansseissa objekteja, mutta esimerkissä niitä ei poisteta missään vaiheessa. Käytännön pelissä, jollaisesta teen esimerkin tutoriaalin kolmanteen osaan, kuitenkin yleensä jossain vaiheessa poistetaan instansseja, ja poistossa täytyy muistaa ENNEN type-instanssin tuhoamista vapauttaa myös kuvat ja/tai objektit.

EDIT 2009: Tutoriaalin toiset osat postattuina alla

Toinen osa käsittelee ainakin viitteiden välittämistä funktioihin ja takaisin niistä, viitteiden tallentamista typen sisälle ja instanssien lisäämistä eri kohtiin listaa, mahdollisesti jotain muutakin, jos tulee mieleen. Kolmanteen ja viimeiseen osaan olisi tarkoitus tehdä yksinkertainen tässä ja toisessa osassa esiteltyjä asioita hyödyntävä peliesimerkki, ja kirjoittaa selostus koodin toiminnasta.

Tässä vielä esimerkkikoodipätkistä koostettu pieni ohjelma, joka esittelee yllä läpikäytyjä asioita:

Code: Select all

//Auton määritelmä
Type Auto
	Field Merkki$			//Auton merkki (Esim. "Audi", "BMW")
	Field Mallinimi$		//Auton mallin nimi (Esim. "A6", "Roadster")
	Field Vuosimalli%		//Auton vuosimalli (Esim. 2006)
	Field Kulutus#			//Polttoainekulutus per 100km (Esim. 7.3)
End Type


//Luodaan auto-määritelmän mukainen instanssi, ja tallennetaan viite
Auto.Auto = New(Auto)

//Asetetaan juuri luodun auto-instanssin ominaisuudet
Auto\Merkki$ = "Teat" //Telajärven autotehdas [url=http://www.saunalambusplaza.net/teat/]http://www.saunalambusplaza.net/teat/[/url]   ;)
Auto\MalliNimi$ = "Wanker 4x4 ST"
Auto\Vuosimalli% = 2006
Auto\Kulutus# = 46.0

//Poistetaan äsken luotu auto
Delete Auto.Auto

//Luodaan 10 autoa
For i = 1 To 10
    //Tallennetaan saatu viite aina samaan viitemuuttujaan
	Auto.Auto = New(Auto)
    
    Auto\Merkki$ = "Auto"
    Auto\Mallinimi$ = "Malli"
    //Asetetaan vuosimalliksi 2000 + for-silmukan indeksi i
    //Vuosimalleiksi tulee siis ensimmäiselle instanssille 2001 (2000 + 1),
    //toiselle 2002 (2000 + 2) jne., aina 2010 asti)
    Auto\Vuosimalli% = 2000 + i
    Auto\Kulutus# = 15.5
    
Next i

//Tulostetaan jokaisen autoinstanssin vuosimalli
For AutoViite.Auto = Each Auto
    Print AutoViite\Vuosimalli%
    //Exit  //Jos komennon edestä poistaa kommentoinnin, silmukan suoritus
            //keskeytyy heti ensimmäisen tulostuksen jälkeen, ja AutoViite
            //jää osoittamaan ensimmäiseen instanssiin
Next AutoViite
//Jos koko silmukka on suoritettu läpi, AutoViite osoittaa lopuksi NULLiin

//Tarkistetaan viite
If AutoViite = NULL Then
    Print "AutoViite ei osoita mihinkään instanssiin!"
Else
    Print "AutoViite osoittaa instanssiin!"
EndIf

WaitKey
Aiemmin väsäämäni (sekavahko) graafinen esimerkki, käyttää CB:n mukana tulevaa mediaa (copypastea editoriin ja aja):

Code: Select all

//Typejen käytön esimerkki

//Yksinkertainen esimerkkitype
Type Esimerkki
    Field Obj%      //Instanssin objekti    
End Type



//"Normi"alustukset
SCREEN 640, 480, 0, 1
Randomize Timer()

//Ladataan prototyyppi (master) -objekti, ja piilotetaan se
//Kaikki näkyvät objektit kloonataan tästä
Proto_Objekti = LoadObject("Media\guy.bmp")
ShowObject Proto_Objekti, OFF



//Lasketaan ruudun keskipiste
RuutuKeskiX = ScreenWidth() / 2
RuutuKeskiY = ScreenHeight() / 2


//Luodaan 10 Esimerkki-typen instanssia
For i = 1 To 10    

    Text 20, 20, "Luodaan Esimerkki-typen instansseja..."

    //Luodaan instanssi, ja otetaan sen viite Esim.Esimerkki-viitemuuttujaan
    Esim.Esimerkki = New(Esimerkki)

    //Asetetaan koordinaatit ympyrään ruudun keskipisteen ympärille
    X# = RuutuKeskiX + Cos(36 * i) * 100
    Y# = RuutuKeskiY + Sin(36 * i) * 100

    //Kopioidaan prototyyppi-objekti
    Esim\Obj% = CloneObject(Proto_Objekti)
    
    
    //Sijoitetaan instanssin objekti, tässä tapauksessa näyttökoordinaattien mukaan
    ScreenPositionObject Esim\Obj%, X#, Y#

    //Piirretään ruutu
    DrawScreen      

    //Odotetaan hetki, että käyttäjä ehtii nähdä muutokset
    Wait 100
    
Next i

    //Kerrotaan käyttäjälle/katsojalle, mitä tapahtuu
    Text 20, 20, "Nyt on luotu 10 type-instanssia (esiintymää), joilla jokaisella on"
    Text 20, 40, "oma objekti-kenttänsä (Esim\Obj%). Kenttä on tavallinen kokonaisluku-"
    Text 20, 60, "muuttuja, se vain sijaitsee type-instanssissa, ja siihen viitataan"
    Text 20, 80, "viitemuuttujan läpi (esim.  MoveObject Esim\Obj, -1, 1)"
    Text 20, 380, "Esim.Esimerkki -viitemuuttuja osoittaa tällä hetkellä viimeisenä"
    Text 20, 400, "luotuun instanssiin. Viitemuuttuja voidaan asettaa osoittamaan"
    Text 20, 420, "mihin tahansa Esimerkki-instanssiin. Paina jotain jatkaaksesi"    
    DrawScreen 

    WaitKey    

    //Silmukka käydään kahdesti läpi
    For i = 1 To 2
        Locate 20, 20
        AddText "Liikutetaan Esim.Esimerkki-viitemuuttujan objektia Esim\Obj%"
        AddText "Kun objekti palaa paikalleen, paina jotain jatkaaksesi"
    
        //Liikutetaan objektia jonkun verran taakse
        For j = 1 To 100
            MoveObject Esim\Obj%, -1
            DrawScreen        
        Next j
        
        //Liikutetaan objektia jonkun verran eteen
        For j = 1 To 100
            MoveObject Esim\Obj%, 1
            DrawScreen
        Next j
    
        WaitKey
        
        ClearText
        
        Locate 20, 400
        AddText "Vaihdetaan Esim.Esimerkki osoittamaan ensimmäiseen listan instanssiin"
        AddText "(Esim.Esimerkki = First(Esimerkki) ja liikutetaan taas objektia samoin"
        AddText "kuin äsken."
        
        //Vaihdetaan Esim.Esimerkki osoittamaan Esimerkki-typelistan ensimmäiseen instanssiin
        Esim.Esimerkki = First(Esimerkki)
    Next i
    
    ClearText
    
    Text 20, 20, "Vaikka molemmilla kerroilla liikutettiin Esim.Esimerkki -viitemuuttujan"
    Text 20, 40, "Obj% -kentän objektia, ruudulla liikkuikin eri objekti."
    Text 20, 380, "Selitys: Esim.Esimerkki, jonka kenttä (field) Obj% sisältää liikutettavan"
    Text 20, 400, "objektin, vaihdettiin osoittamaan listan ensimmäistä instanssia."
    Text 20, 420, "Vaikka Esim\Obj% -objektia käsiteltiin samalla tapaa, oli kyseessä tällä"
    Text 20, 440, "kertaa eri objekti kuin ensimmäisellä liikutuskerralla."
    Text 20, 460, "Paina jotain jatkaaksesi"
    DrawScreen
    WaitKey
    
    Locate 20, 20
    AddText "Nyt käydään jokainen listassa oleva instanssi läpi for...each -rakenteella."
    AddText "Ruudun keskeltä lähtevä viiva näyttää, minkä objektin type-instanssia"
    AddText "ollaan käsittelemässä."
    Locate 20, 400
    AddText "Sama Esim.Esimerkki -viitemuuttuja siis osoittaa vuorollaan jokaiseen"
    AddText "instanssiin, kunnes kaikki listassa olevat instanssit on käyty läpi."
    AddText "Esim.Esimerkki siirtyy aina seuraavaan instanssiin, kun saavutaan"
    AddText "riville jossa on komento Next Esim .  Paina jotain jatkaaksesi"
    DrawScreen
    WaitKey

    //Käydään koko Esimerkki-lista läpi
    For Esim.Esimerkki = Each Esimerkki
        
        //Liikutetaan taakse
        For i = 1 To 20
            MoveObject Esim\Obj%, -2
            //Piirretään viiva ruudun keskipisteestä objektiin
            Line RuutuKeskiX, RuutuKeskiY, RuutuKeskiX + ObjectX(Esim\Obj%), RuutuKeskiY - ObjectY(Esim\Obj%)
            DrawScreen        
        Next i

        //Liikutetaan eteen
        For i = 1 To 20
            MoveObject Esim\Obj%, 2
            //Piirretään viiva ruudun keskipisteestä objektiin
            Line RuutuKeskiX, RuutuKeskiY, RuutuKeskiX + ObjectX(Esim\Obj%), RuutuKeskiY - ObjectY(Esim\Obj%)
            DrawScreen        
        Next i
        
    Next Esim

    ClearText
    
    Text 20, 20, "For Esim.Esimerkki = Each Esimerkki  ...   Next Esim  -rivien välinen koodi"
    Text 20, 40, "siis suoritetaan niin monta kertaa, kuin listasta löytyy instansseja."
    Text 20, 380, "Joka kerta, kun saavutaan Next Esim riville, Esim -viitemuuttuja siirtyy"
    Text 20, 400, "listalla eteenpäin osoittamaan nykyistä seuraavaa instanssia (josta siis"
    Text 20, 420, "tulee Esim -muuttujan osoittama 'nykyinen' instanssi), kunnes lista on käyty"
    Text 20, 440, "läpi. Näin esimerkiksi kaikki viholliset voidaan päivittää yhdessä silmukassa,"
    Text 20, 460, "eikä ohjelmoijan tarvitse muuttaa mitään, oli instansseja 1 tai 1000."
    DrawScreen
    WaitKey


Re: Arkistojen kätköistä: Type-esimerkki

Posted: Fri May 08, 2009 9:07 pm
by ezbe
Osa 2A. Kehittyneempi typejen käsittely: Muut listojen läpikäyntitavat

Oppaan ensimmäinen osa käsitteli typejen käytön perusasioita. Nyt käsittelemme monipuolisempia tapoja hallita typejä ja erilaisia "kikkoja", joilla niistä saadaan kaikki hyöty irti. Olisi suositeltavaa osata tai ainakin jotenkuten ymmärtää seuraavat asiat jo ennestään: If-lauseet, While...Wend -silmukoiden toimintaperiaate, mikä on lista (siis se "sisäinen" linkitetty lista, johon luodut instanssit tallentuvat, ja josta puhuttiin ensimmäisessä osassa) sekä viitemuuttujien käyttö. Myös funktioiden toimintaperiaatteen ymmärtäminen auttaa myöhemmissä vaiheissa, joissa käsitellään typejen käyttöä funktioiden kanssa.

Kaikissa seuraavissa lista- ja Insert-esimerkeissä on käytetty tällaista yksinkertaista esimerkki-typeä:

Code: Select all

Type Esimerkki
    Field Obj%  //Instanssin objekti
    Field HP%   //Instanssin kestopisteet (health)
End Type

Heti ensimmäisenä ajattelin käydä läpi neljä uutta typeihin liittyvää komentoa, joita aiemmassa osassa ei vielä käytetty (Paitsi Firstiä graafisessa esimerkissä): First, Last, After ja Before. Englantia osaavat luultavasti pystyvät päättelemäänkin jo komentojen tarkoitukset niiden nimistä. Jokainen näistä komennoista palauttaa viitteen, joka voidaan tallentaa viitemuuttujaan (joiden käyttöä siis käytiin läpi ensimmäisessä osassa). Käydäänpä komennot pikaisesti läpi:


First([Type-määrittelyn nimi])
Tämä komento palauttaa viitteen listan ensimmäiseen instanssiin. Tulos on NULL, jos ainuttakaan instanssia ei ole olemassa.

Käyttöesimerkki:

Code: Select all

Esim.Esimerkki = First(Esimerkki)   //Palauttaa ensimmäisenä listassa olevan Esimerkki-instanssin
                                    //viitteen ja tallentaa sen Esim.Esimerkki -viitemuuttujaan

Last([Type-määrittelyn nimi])
Tämä komento palauttaa viitteen listan viimeiseen instanssiin. Tulos on NULL, jos ainuttakaan instanssia ei ole olemassa.

Käyttöesimerkki:

Code: Select all

Esim.Esimerkki = Last(Esimerkki)    //Palauttaa viimeisenä listassa olevan Esimerkki-instanssin
                                    //viitteen ja tallentaa sen Esim.Esimerkki -viitemuuttujaan
Jos listassa on vain yksi instanssi, First ja Last palauttavat molemmat saman instanssin (koska se on sekä ensimmäinen että viimeinen).

After([Viitemuuttuja])
Tämä komento palauttaa viitteen annettua instanssia (annetaan viitemuuttujan kautta) SEURAAVAAN instanssiin. Jos annettu instanssi oli listan viimeinen (eli seuraavaa instanssia ei ole), on tulos NULL. Huomaa, että annetun viitemuuttujan on osoitettava johonkin instanssiin ennen tämän komennon käyttöä.

Käyttöesimerkki:

Code: Select all

Esim.Esimerkki = After(Esim.Esimerkki)  //Palauttaa Esim.Esimerkki -viitemuuttujan viittaamaa instanssia
                                        //seuraavan instanssin, ja tallentaa sen Esim.Esimerkki -viitemuuttujaan
//Eli, jos viitemuuttuja osoitti ENNEN tätä komentoa esimerkiksi listan ensimmäiseen instanssiin, se osoittaa
//komennon suorittamisen JÄLKEEN listan toiseen instanssiin (tai NULL:iin, jos annettu viitemuuttuja osoitti listan ainoaan tai viimeiseen instanssiin)
Käytännössä For...Each-rakenteessa Next [tunnuksen nimi tähän] -rivillä siis suoritetaan tämän kaltainen operaatio, eli For -lauseessa annettu tunnus asetetaan osoittamaan nykyistä instanssia seuraavaan instanssiin. Jos ajatellaan tätä ensimmäisessä osassa olleen linkitetyn listan toimintaa esittävän kuvan kautta, After palauttaa seuraava -nuolen osoittaman instanssin.


Before([Viitemuuttuja])
Tämä komento palauttaa viitteen annettua instanssia (annetaan viitemuuttujan kautta) EDELTÄVÄÄN instanssiin. Jos annettu instanssi oli listan ensimmäinen (eli edeltävää instanssia ei ole), on tulos NULL. Huomaa, että annetun viitemuuttujan on osoitettava johonkin instanssiin ennen tämän komennon käyttöä.

Käyttöesimerkki:

Code: Select all

Esim.Esimerkki = Before(Esim.Esimerkki)  //Palauttaa Esim.Esimerkki -viitemuuttujan viittaamaa instanssia
                                        //seuraavan instanssin, ja tallentaa sen Esim.Esimerkki -viitemuuttujaan
//Eli, jos viitemuuttuja osoitti ENNEN tätä komentoa esimerkiksi listan viimeiseen instanssiin, se osoittaa
//komennon suorittamisen JÄLKEEN listan toiseksi viimeiseen instanssiin (tai NULL:iin, jos annettu viitemuuttuja osoitti listan ainoaan tai ensimmäiseen instanssiin)
Jos ajatellaan tätä ensimmäisessä osassa olleen linkitetyn listan toimintaa esittävän kuvan kautta, Before palauttaa edellinen -nuolen osoittaman instanssin. Tätä voidaan siis käyttää hakemaan tiettyä instanssia edeltävä instanssi. Komentoja voidaan myös yhdistää:

Code: Select all

Esim.Esimerkki = Before(Last(Esimerkki)) //Palauttaa viimeistä edeltävän instanssin
Näitä komentoja käytetään myös Insert -komennon kanssa, mutta palaan siihen myöhemmin.


Nyt voidaan jatkaa aiheesta, johon ensimmäinen osa päättyi: listojen läpikäynti. Ensimmäisenä lähdetään liikkeelle mielestäni yksinkertaisimmasta tapauksesta: For...Each:n rakentaminen edellä esitettyjen komentojen avulla. Tässähän ei tietysti siinä mielessä ole mitään järkeä, että CB:ssä tosiaan on jo hyvin toimiva For...Each -rakenne, mutta sen toteutus on niin yksinkertainen, että sillä on helpointa esittää First:n ja After:n toimintaa.

For...Each -rakennetta voitaisiin käyttää Esimerkki-typen kanssa tähän tapaan:

Code: Select all

//Esim.Esimerkki asetetaan osoittamaan listan ensimmäistä instanssia,
//ja jos sellainen on, siirrytään silmukan sisäpuolelle
For Esim.Esimerkki = Each Esimerkki
    //Typelle voitaisiin tehdä tässä välissä jotain
//Haetaan seuraava instanssi, ja jos sellaista ei ole, poistutaan silmukasta
Next Esim
Tämähän lienee jo tuttua oppaan ensimmäisestä osasta. Lähdetään siis liikkeelle For...Each:n toteuttamisesta First:llä ja Afterilla:

Code: Select all

// ------------- For Esim.Esimerkki = Each Esimerkki ----------------
    //Haetaan viitemuuttujaan listan ensimmäinen instanssi
    Esim.Esimerkki = First(Esimerkki)
    
    //Aletaan suorittaa silmukkaa, jos saatiin ensimmäinen instanssi (eli jos While -lauseen sulkujen välissä oleva ehto on tosi)
    While(Esim <> NULL)
// ------------- For Esim.Esimerkki = Each Esimerkki ----------------
        
        //Tässä kohtaa voitaisiin tehdä esim. typen kentille jotain, 
        //samoin kuin yleensä For...Each - Next  lauseiden välissä

// ------------- Next Esim ----------------        
        //Haetaan nykyistä seuraava instanssi
        Esim.Esimerkki = After(Esim.Esimerkki)
        
    //Wend -lauseen kohdalla suoritus palaa While -lauseen ehtotarkistukseen, ja
    //jos ehto ei pidä enää paikkaansa, siirrytään Wend:iä seuraavalle riville
    Wend
// ------------- Next Esim ----------------
Eli, // ---- -kommentein merkittyjen rivien välissä on vastaavan komennon toteutus muilla keinoin. Kun For...Each -silmukka alustetaan, hakee se annettuun viitemuuttujaan ensin listan ensimmäisen instanssin. Tämä on toteutettu esimerkkikoodissa First:n avulla, joka siis hakee Esim.Esimerkki-viitemuuttujaan ensimmäisen Esimerkki-instanssin listan alusta. Suoritus siirtyy While-silmukan sisäpuolelle, jos listassa on yksikin instanssi, eli Esim.Esimerkki sisältää viitteen johonkin muualle kuin NULLiin. Silmukan sisäpuolella voitaisiin instanssille tehdä mitä ikinä tarvitseekaan, samoin kuin For...Each -silmukassa varsinaisen For -rivin ja Next -rivin välillä. Lopussa on Next Esim -lauseen toteutus, eli haetaan nykyistä instanssia seuraava instanssi samaan viitemuuttujaan, ja aloitetaan silmukan suoritus alusta. Kannattaa tosiaan huomata, että tämä lause (Esim.Esimerkki = After(Esim.Esimerkki) ) hakee siis todellakin ITSEÄÄN seuraavan instanssin, ja tallentaa sen viitteen "itsensä päälle". Tämän jälkeen Esim.Esimerkki osoittaa siis siihen instanssiin, jonka edeltävää instanssia se osoitti ennen After-riviä. Tässä kohtaa tapahtuu uudelleen While-lauseen ehdon tarkistus, ja jos ollaan tultu listan loppuun, osoittaa Esim.Esimerkki NULLiin ja silmukan suoritus päättyy.

Tällaista rakennetta ei tietenkään ole tarpeen käyttää, sehän vain toteuttaa saman asian kuin For...Each -silmukkarakennekin. Jos Esim.Esimerkki -viitemuuttujaan kuitenkin sijoitetaan viite johonkin muuhun, kuin listan ensimmäiseen instanssiin, se käykin läpi ainoastaan kaikki ko. instanssia seuraavat instanssit. Tätä hyödynnetään hetken kuluttua, kun katsotaan miten tehdä törmäystarkistukset tehokkaasti saman listan instanssien kesken.

Huomionarvoista on myös, että jos silmukassa poistetaan jokin instanssi viitemuuttujan läpi, on viite seuraavaan otettava jo ennen poistoa talteen johonkin toiseen viitemuuttujaan, koska poiston jälkeen viite on NULL, eikä After(...) -komento pysty palauttamaan seuraavaa instanssia NULL:sta! For...Each:n kanssa vastaavaa ongelmaa ei ilmene, ilmeisesti se pitää viitteen seuraavaan instanssiin tallessa jossain muualla, eli instanssin poisto keskellä silmukkaa ei YLEENSÄ aiheuta ongelmia. Poikkeuksena sääntöön on, jos viitteen läpi yritetään tehdä mitään poiston jälkeen, ennen kuin viite osoittaa jo johonkin toiseen instanssiin, kaatuu ohjelma jälleen MAV:iin (NULL-viitteen läpi ei saa KOSKAAN yrittää tehdä mitään!).

Nyt kuitenkin otetaan vielä toinen yksinkertainen esimerkki. Tällä kertaa kuljetaankin listaa toiseen suuntaan, eli viimeisestä instanssista ensimmäiseen. Silmukan toimintaperiaate on vastaavanlainen kuin edellä, nyt vain siis kuljetaan toiseen suuntaan:

Code: Select all

    //Haetaan listan viimeinen instanssi viitemuuttujaan
    Esim.Esimerkki = Last(Esimerkki)

    //Suoritetaan silmukkaa niin kauan, kunnes Esim osoittaa NULLia
    While(Esim <> NULL)
        //Tässä voitaisiin tehdä typelle jotain...
        
        //Haetaan nykyistä edeltävä instanssi
        Esim.Esimerkki = Before(Esim.Esimerkki)
    //Wend:n kohdalla tehdään taas ehtotarkistus, ja jos se pitää paikkansa, silmukka suoritetaan uudelleen
    //Jos ollaan tultu jo listan alkuun, ensimmäistä edeltävä on taas NULL, ja poistutaan silmukasta
    Wend


Sisäkkäiset silmukat ovat käteviä vertailu- ja tarkistusrakenteiden tekemisessä. Jos verrataan vaikkapa jokaista ammusta jokaiseen pelihahmoon, käytetään kahta sisäkkäistä For...Each -silmukkaa, joista toinen käy läpi hahmot ja toinen ammukset, ja näiden välistä törmäystä tarkistetaan.

Code: Select all

Type Ammus
    Field Obj%
    Field Vahinko%
End Type

Type PeliHahmo
    Field Obj%
    Field HP%
End Type

//Törmäystarkistus
//Käydään läpi pelihahmot
For Hahmo.PeliHahmo = Each PeliHahmo
    //Käydään läpi ammukset
    For Am.Ammus = Each Ammus
        //Tarkistetaan, törmääkö Ammus-instanssin objekti PeliHahmo-instanssin objektiin
        If(ObjectsOverlap(Am\Obj%, Hahmo\Obj%)) Then
            //Vähennetään pelihahmon hp:stä ammuksen vahinko
            Hahmo\HP% = Hahmo\HP% - Am\Vahinko%
            
            //Poistetaan ammuksen objekti ja instanssi
            DeleteObject Am\Obj%    //Jos tätä ei tehdä, objekti jää muistiin vaikka instanssi poistetaan!!
            Delete Am.Ammus         //Poistetaan instanssi
        EndIf
    Next Am
Next Hahmo
Yllä oleva koodi ei yksinään tee mitään, sillä siitä puuttuu ammusten ja pelihahmojen luonti ja päivitys, sekä ruudun piirto. Ideana on vain esittää, miten kahden erityyppisen instanssin objektien välinen törmäys voitaisiin tarkistaa sisäkkäisillä For..Each:eilla.


HUOM! Koska ammus- ja pelihahmo- esimerkissä sisemmässä silmukassa poistetaan Ammus-instanssi, on sillä merkitystä, kumpaa selataan sisä- ja kumpaa ulkosilmukassa! Miksikö? Jos Ammus-instanssit selattaisiin ulommalla silmukalla, voisi syntyä tilanne, jossa sisempi silmukka on menossa PeliHahmo-listan keskellä. Ammus törmää pelihahmoon, ja se poistetaan. Seurauksena, kun sisempi silmukka siirtyy seuraavaan PeliHahmoon, Am-viite onkin NULL, ja ohjelma kaatuu ObjectsOverlap:in kohdalla, koska NULL-viitteen läpi yritetään lukea Am\Obj% -kenttää! Tällaisia ajatusvirheitä sattuu hyvin helposti, joten kannattaa aina käydä mielessään tai pienellä testiohjelmalla läpi, miten silmukoiden toiminta itseasiassa etenee suorituksen aikana. On aina pidettävä huolta, että jos jossain kohtaa poistetaan viitemuuttujan kautta jokin instanssi, viitemuuttujan läpi ei yritetä tehdä mitään, ennen kuin siihen on taas saatu uusi viite (tai vähintäänkin tarkistetaan viitemuuttuja NULL:n varalta ennen sen käyttöä)!


Sisäkkäisiä silmukoita käyttämällä voidaan verrata myös saman listan kahta erillistä instanssia toisiinsa. Näin saatettaisiin tehdä esimerkiksi, jos tarvitsee tarkistaa kahden saman tyyppisen instanssin objektien törmäystä toisiinsa (esim. kaikkien samalla typellä toteutettujen pelihahmojen väliset törmäykset pitäisi tarkistaa). Koska molemmat objektit sijaitsevat samassa listassa, voitaisiin käyttää kahta sisäkkäistä For...Each -silmukkaa. Mutta on olemassa syy, miksi näin ei kannata tehdä tilanteessa, jossa verrataan samassa listassa sijaitsevia objekteja! Katsotaan kuitenkin nyt ensiksi, miten törmäystarkistus tehtäisiin kahdella sisäkkäisellä For...Each -silmukalla samassa listassa sijaitsevien instanssien objekteille:

Code: Select all

    //Käydään lista läpi
    For Esim.Esimerkki = Each Esimerkki
        //Käydään lista läpi uudelleen, tällä kertaa viitteet tallennetaan toiseen viitemuuttujaan
        For Esim2.Esimerkki = Each Esimerkki
            //Jos kyseessä ei ole sama instanssi...
            If Esim2 <> Esim Then
                //Tarkistetaan objektien törmäys
                If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then
                    //Jos Objektit törmäävät, vähennetään hp:tä molemmilta
                    Esim\HP% = Esim\HP% - 1
                    Esim2\HP% = Esim2\HP% - 1
                EndIf
            EndIf
        Next Esim2
    Next Esim
Yllä olevassa koodissa oletetaan että ko. typestä on luotu jo instansseja, ja näillä instansseilla on jokin objekti, joka on tallennettu Obj -kenttään (itseasiassa ko. objektin "handle" eli "kahva", joka on myöskin eräänlainen viite, on tallennettu kyseiseen kenttään, mutta se on sivuseikka).

Ensimmäinen For...Each -silmukka käy siis läpi kaikki instanssit tallentaen viitteen aina Esim.Esimerkki -viitemuuttujaan, ja toinen tekee samoin, mutta tallentaa viitteen Esim2.Esimerkki -viitemuuttujaan. Sisemmän silmukan sisällä tarkistetaan ensin, etteivät molemmat viitteet osoita samaa instanssia. If Esim2 <> Esim -lohkon sisäinen koodi siis suoritetaan vain, kun Esim2- ja Esim- viitemuuttujien osoittamat instanssit eivät ole yksi ja sama instanssi (mainitsin ensimmäisessä osassa NULL:in käsittelyn yhteydessä, että samaa type-määrittelyä käyttäviä viitemuuttujia voidaan verrata keskenään). Jos tätä tarkistusta ei olisi, tarkistettaisiin jokaisen objektin törmäys myös sen itsensä kanssa, jolloin ne törmäisivät jokaisella törmäystarkistuskerralla itseensä, ja kuolisivat pian pois. Viimeisenä silmukan sisällä tarkistetaan vasta objektien törmäys, tässä tapauksessa ObjectsOverlapilla, toki sen voi tehdä muullakin tavoin. Jos Objektit törmäävät, molemmilta vähennetään HP -kentästä yksi (1).

Jos kyseessä olisivat erityyppiset instanssit (kuten aikaisemmin Hahmo.PeliHahmo ja Am.Ammus), tämä olisi oikea lähestymistapa, ja silloin ei tietenkään tarvittaisi tuota If Esim <> Esim2 -tarkistusta, koska:


CB antaa suorittaa myös kahden eri tyyppi-määrittelyn instanssin välisen vertailun, tästä ei kuitenkaan ole mitään hyötyä koska sanoohan jo järkikin, etteivät ne voi olla yksi ja sama. Pahimmassa tapauksessa kuitenkin If-lause saattaisi tulkita kaksi eri tyyppistä instanssia samaksi instanssiksi, jos niiden viite sattuu olemaan samanlainen, tästä syystä eri tyyppi-määrittelyjen instansseja ei tule koskaan verrata! En ole kokeillut, voiko näin itseasiassa tapahtua, mutta ainakin koodi, jossa verrataan kahta erityyppistä viitemuuttujaa, menee syntaksitarkistuksesta läpi että heilahtaa.


Demonstroin nyt koodin toimintaa selostuksella, jos oletetaan että listassa on kolme instanssia, joilla on omat objektinsa. Jos ymmärsit ylläolevasta selostuksesta, miten sisäiset silmukat toimivat tässä tapauksessa, voit huoletta hypätä tämän yli. Pahoittelen myös mahdollista selostuksen sekavuutta, en ikävä kyllä keksinyt parempaakaan tapaa kuvata koodin suoritusta tavallisen selostuksen lisäksi:
Italiccina (eli näin) kirjoitettu teksti tarkoittaa yleistä kommenttia tai selostusta, mitä missäkin tapahtuu
Boldina (eli näin) kirjoitettu teksti on koodia
Normaalina (eli näin) nuoli-merkinnän ( -> ) jälkeen näkyvä teksti kertoo seurauksia koodin suorittamisesta

Alussa saavutaan riville For Esim.Esimerkki = Each Esimerkki, oletetaan että Esimerkki-instansseja on luotu 3 kpl, jokaisella oma objektinsa jne.
Oletetaan myös, että instanssien 2 ja 3 objektit ovat sijoittuneet siten että ne törmäävät
Merkitsen viitemuuttujien osoitusta aina Esim = [monesko instanssi] ja Esim2 = [monesko instanssi]

For Esim.Esimerkki = Each Esimerkki -> Esim = 1

Saavutaan riville For Esim2.Esimerkki = Each Esimerkki
For Esim2.Esimerkki = Each Esimerkki -> Esim2 = 1

Verrataan sisältöjä, huomataan että ne osoittavat samaa instanssia, eli ei suoriteta lohkon sisällä sijaitsevaa törmäystarkistusta
If Esim <> Esim2 Then -> 1 <> 1 Ei pidä paikkaansa, 1 = 1, eli if-lohkon sisäistä koodia ei suoriteta

Saavutaan riville Next Esim2, eli Esim2 osoittaa seuraavaan instanssiin, Esim osoittaa edelleen 1:een
Next Esim2 -> Esim2 = 2

Nyt Esim ja Esim2 osoittavat eri instansseihin, joten if-lohkon sisälle päästään ja törmäystarkistus tapahtuu

If Esim <> Esim2 Then -> 1 <> 2 totta, suoritetaan if-lohko
1 ja 2 eivät törmää Kuten aiemmin mainittiin, oletetaan että tilanne on sellainen, että vain instanssien 2 ja 3 objektit törmäävät
Next Esim2 -> Esim2 = 3

If Esim <> Esim2 Then -> 1 <> 3 totta, suoritetaan if-lohko
1 ja 3 eivät törmää
Next Esim2 -> Esim2 = NULL
Tässä kohtaa Esim2 -viitemuuttuja on saapunut listan loppuun, ja siirrytään siis Next Esim2 -riviä seuraavalle riville

Nyt liikutetaan Esim.Esimerkki -viitemuuttujaa (eli ulomman For...Each -silmukan viitemuuttujaa) eteenpäin listalla
Next Esim -> Esim = 2
Palataan silmukan alkuun, sisempi silmukka alustetaan taas uudelleen
For Esim2.Esimerkki = Each Esimerkki -> Esim2 = 1

If Esim <> Esim2 Then-> 2 <> 1 totta, suoritetaan if-lohko
2 ja 1 eivät törmää
Next Esim2 -> Esim2 = 2

If Esim <> Esim2 Then-> 2 <> 2 Ei pidä paikkaansa, eli if-lohkon sisäistä koodia ei suoriteta
Next Esim2 -> Esim2 = 3

If Esim <> Esim2 Then -> 2 <> 3 totta, suoritetaan if-lohko
2 ja 3 TÖRMÄÄVÄT Kuten alkuoletuksissa sanottiin
Vähennetään HP:tä instanssilta 2 Esim.Esimerkin osoittama instanssi -> Esim\HP
Vähennetään HP:tä instanssilta 3 Esim2.Esimerkin osoittama instanssi -> Esim2\HP
Next Esim2 -> Esim2 = NULL

Taas lähdetään alusta, nyt Esim.Esimerkki siirtyy osoittamaan listan kolmatta, ja samalla viimeistä instanssia
Next Esim -> Esim = 3
Taas alustetaan sisäsilmukka uudelleen
For Esim2.Esimerkki = Each Esimerkki -> Esim2 = 1

If Esim <> Esim2 Then-> 3 <> 1 totta, suoritetaan if-lohko
3 ja 1 eivät törmää
Next Esim2 -> Esim2 = 2

If Esim <> Esim2 Then-> 3 <> 2 totta, suoritetaan if-lohko
3 ja 2 TÖRMÄÄVÄT Kuten alkuoletuksissa sanottiin
Vähennetään HP:tä instanssilta 3 Esim.Esimerkin osoittama instanssi -> Esim\HP
Vähennetään HP:tä instanssilta 2 Esim2.Esimerkin osoittama instanssi -> Esim2\HP
Next Esim2 -> Esim2 = 3

If Esim <> Esim2 Then-> 3 <> 3 Ei pidä paikkaansa, eli if-lohkon sisäistä koodia ei suoriteta
Next Esim2 -> Esim2 = NULL

Next Esim -> Esim = NULL

Ja näin sisäkkäiset silmukat on suoritettu loppuun

Jos katsotaan vertailujärjestystä, merkiten instanssit järjestyslukuina, vertailut suoritettiin näin (sulkuihin merkityt jäävät tarkistamatta, koska kyseessä on sama instanssi):
Mitä -> mihin
Esim Esim2
(1 -> 1)
1 -> 2
1 -> 3
2 -> 1
(2 -> 2)
2 -> 3 (Törmäys)
3 -> 1
3 -> 2 (Sama törmäys tunnistetaan uudelleen)
(3 -> 3)
Mikä tässä sitten muka on vialla? Kaikki objektit käydään läpi, ja kaikki törmäykset kyllä huomataan, jos niitä tapahtuu. Esimerkin mukaisesti samaa listaa läpikäytäessä kahdella eri viitteellä tässä tulee kuitenkin kaksikin ongelmaa: Jo jälkimmäisestä (sekavasta) askel kerrallaan suorituksen selostavasta osasta huomataan, että instanssien 2 ja 3 objektien välinen törmäys tarkistetaan kahdesti, ja näin ollen ne menettävät kaksi hitpointtia (hp) molemmat! Ensimmäisellä kerralla viite Esim osoittaa instanssia 2 ja Esim2-viite instanssia 3, jälkimmäisellä kerralla toisinpäin. Koska instanssin 2 objekti törmää instanssin 3 objektiin, täytyy saman tapahtua myös toisinpäin. Sama vertailu tapahtuu kaikkien instanssien objektien kohdalla, aina molempiin suuntiin. Teemme siis valtavan määrän turhia tarkistuksia, joka hidastaa ohjelman suoritusta, ja lisäksi se käytännössä tuplaa vahingon, koska sama tarkistus suoritetaan turhaan kahdesti!

Jos mietitään hetki, miten nuo kannattaisi tarkistaa, niin nopeasti tajuamme, että jos instanssin 1 objektia on jo verrattu kaikkiin sen jälkeisten instanssien objekteihin, ei niitä kannata enää verrata myöhemmin instanssin 1 objektiin, koska törmäystarkistus tuottaa aina saman tuloksen, riippumatta kumpaa verrataan kumpaan. Tässä kohtaa meidän tarvitsee siis käydä listaa läpi hieman eri tavalla, jolloin sekä vertailujen määrä pienenee, ja samalla pääsemme eroon tuplavahinko-ongelmasta.

Sisäkkäisiä silmukoita käytetään edelleen, mutta tällä kertaa sisempi silmukka siis käy läpi vain kaikki ne instanssit, jotka tulevat ulomman silmukan viitemuuttujan osoittaman instanssin JÄLKEEN. Jos muistat alussa käydyn For...Each:n toteutuksen After:n ja While-silmukan avulla, arvannet miten tämä toteutetaan:

Code: Select all

    //Käydään lista läpi
    For Esim.Esimerkki = Each Esimerkki
        //Haetaan Esim.Esimerkki-viiteosoittimen osoittaman instanssin jälkeen tuleva instanssi
        Esim2.Esimerkki = After(Esim.Esimerkki)
        //Suoritetaan sisäsilmukkaa (While...Wend) niin kauan, kuin Esim2.Esimerkki osoittaa johonkin instanssiin    
        While(Esim2 <> NULL)
            //Tarkistetaan törmäys
            If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then
                //Jos Objektit törmäävät, vähennetään hp:tä molemmilta
                Esim\HP% = Esim\HP% - 1
                Esim2\HP% = Esim2\HP% - 1
            EndIf
                
            //Haetaan Esim2 -viitemuuttujaan sen nykyistä instanssia seuraava instanssi
            //Jos seuraava instanssi = NULL, While...Wend -silmukka päättyy
            Esim2.Esimerkki = After(Esim2.Esimerkki)
        Wend
    //Haetaan Esim.Esimerkki-viitemuuttujaan listan seuraava instanssi
    Next Esim
Tarkastellaanpa jälleen koodin toimintaa: Totuttuun tapaan, alussa alustetaan For...Each -rakenne, jolla käydään kaikki listan instanssit läpi. Tämän jälkeen alustetaan Esim2.Esimerkki -viitemuuttuja, joka osoittaa For...Each -silmukan viitemuuttujan Esim.Esimerkki jälkeiseen (After-komento) instanssiin. Jos siis Esim.Esimerkki osoittaa listan ensimmäiseen instanssiin (kuten se tekee For...Each -silmukan alustuksen jälkeen), Esim2.Esimerkki osoittaa alustuksensa jälkeen listan toiseen instanssiin (jos sellainen on). Tämän jälkeen tullaan While...Wend -silmukkaan. While -komennon yhteydessä oleva ehtotarkistus suoritetaan heti, ja jos Esim2 osoittaa jo NULL:iin (Mahdollista vain, jos listassa oli vain yksi instanssi), ei silmukan sisältöä suoriteta ollenkaan. Mitäpä järkeä siinä olisikaan, jos listalla on vain yksi instanssi, koska ei ole mitään, minkä kanssa törmäystarkistus suoritettaisiin.

Oletetaan kuitenkin esimerkin vuoksi, että listassa on useampia instansseja, jolloin ehtotarkistus pitää paikkansa ja siirrytään silmukan sisälle. Tällä kertaa ei tarvitse tarkistaa, osoittavatko Esim ja Esim2 samaan instanssiin, koska tällainen tilanne ei ole mahdollinen esitetyssä koodissa. Viitemuuttujien instanssien objektien törmäystä verrataan taas ObjectsOverlapilla, ja jälleen vähennetään HP:tä, jos objektit törmäävät. Koska nyt ei ole käytössä For...Each-rakennetta Esim2 -viitteelle, ei seuraavaa instanssia saadakkaan Next Esim2:lla, vaan asetetaan Esim2 -viite osoittamaan itseään seuraavaan instanssiin, kuten näytettiin aikaisemmassa For...Each:n toimintaa simuloivassa silmukassa. Wend-komennon kohdalla suoritus palaa taas While -lauseen ehtotarkistukseen, ja jos tämä ei ole totta, poistutaan silmukasta ja jatketaan koodin suoritusta Wend:iä seuraavalta riviltä. Näin siis käydään aina kaikki Esim -viitettä seuraavat instanssit läpi Esim2-viitteen avulla, ja jokaisen instanssin objektia verrataan aina kaikkien seuraavien instanssien objekteihin, kunnes koko lista on käyty läpi.

Tarkastellaan toimintaa taas samanlaisella rivi-riviltä -selostuksella kuin aikaisemmin:
Italiccina (eli näin) kirjoitettu teksti tarkoittaa yleistä kommenttia tai selostusta, mitä missäkin tapahtuu
Boldina (eli näin) kirjoitettu teksti on koodia
Normaalina (eli näin) nuoli-merkinnän ( -> ) jälkeen näkyvä teksti kertoo seurauksia koodin suorittamisesta

Alussa saavutaan riville For Esim.Esimerkki = Each Esimerkki, oletetaan että Esimerkki-instansseja on luotu 3 kpl, jokaisella oma objektinsa jne.
Oletetaan myös, että instanssien 2 ja 3 objektit ovat sijoittuneet siten että ne törmäävät
Merkitsen viitemuuttujien osoitusta aina Esim = [monesko instanssi] ja Esim2 = [monesko instanssi]

For Esim.Esimerkki = Each Esimerkki -> Esim = 1

Esim2.Esimerkki = After(Esim.Esimerkki) -> Esim2 = 2

While(Esim2.Esimerkki <> NULL) -> 2 <> NULL Totta, siirrytään silmukan sisälle

If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then Esim = 1 ja Esim2 = 2, vain instanssien 2 ja 3 objektit törmäävät, törmäystä ei siis tapahdu

Esim2.Esimerkki = After(Esim2.Esimerkki) -> Esim2 = 3

Tullaan Wend-riville, palataan ehtotarkistukseen
While(Esim2.Esimerkki <> NULL) -> 3 <> NULL Totta, siirrytään silmukan sisälle
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then Esim = 1 ja Esim2 = 3, vain instanssien 2 ja 3 objektit törmäävät, törmäystä ei siis tapahdu

Esim2.Esimerkki = After(Esim.Esimerkki) -> Esim2 = NULL 3. instanssi oli listan viimeinen

Tullaan Next Esim -riville
Next Esim -> Esim = 2

Esim2.Esimerkki = After(Esim.Esimerkki) -> Esim2 = 3

While(Esim2.Esimerkki <> NULL) -> 3 <> NULL Totta, siirrytään silmukan sisälle
If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then Esim = 2 ja Esim2 = 3, törmäys siis tapahtuu

Vähennetään instanssien hp:tä kuten kahta sisäkkäistä For...Each -silmukkaakin käyttävässä esimerkissä

Esim2.Esimerkki = After(Esim2.Esimerkki) -> Esim2 = NULL

Tullaan taas Wend-riville, While -lauseen ehto ei toteudu, eli poistutaan silmukasta

Taas haetaan Esim.Esimerkki-viitteeseen seuraava instanssi, joka on 3. ja listan viimeinen
Next Esim -> Esim = 3

Tällä kertaa Esim.Esimerkillä ei ole seuraavaa instanssia
Esim2.Esimerkki = After(Esim.Esimerkki) -> Esim2 = NULL

Nyt While-ehto ei toteudu, While...Wend -silmukkaan ei siis mennä ollenkaan
While(Esim2.Esimerkki <> NULL) -> NULL <> NULL Epätosi, silmukan sisälle ei siirrytä

Siirretään Esim osoittamaan seuraavaa instanssia
Next Esim -> Esim = NULL

Koska Esim on nyt NULL, silmukka on suoritettu loppuun
Nyt verrattiin siis instanssien objekteja ObjectsOverlapilla näin:
Mitä -> mihin
Esim Esim2
1 -> 2
1 -> 3
2 -> 3 (Törmäys)
Kaikkia verrattiin siis toisiinsa, koska on aivan sama verrataanko 1:stä 2:een, vai 2:sta 1:een. Tässä tapauksessa varsinaisia ObjectsOverlap-tarkistuksia tehtiin siis vain kolme, verrattuna aiemman esimerkin kuuteen tarkistukseen. Suuremmilla instanssimäärillä ero alkaa kasvaa huomattavasti (ensimmäinen vaihtoehto suorittaa n*(n-1) törmäystarkistusta, jossa n = instanssien lukumäärä), ja jos kyseessä on tarkistus, jossa ei ole merkitystä "kummin päin" tarkistus tehdään, on jälkimmäinen tarkistussilmukka huomattavasti tehokkaampi, sen tarkistusmäärän ollessa (n*(n-1)) / 2. Eli sadan (100) samalla type-määritelmällä toteutetun instanssin objektien törmäystarkistus keskenään ensimmäisellä metodilla tarvitsee 9900 tarkistusta, kun jälkimmäisellä selvitetään törmäykset "vaivaisella" 4950:llä tarkistuksella. Suhde pysyy tuossa 2:1 instanssien määrästä riippumatta, eli ensimmäisellä tavalla tarvitaan aina kaksi kertaa enemmän tarkistuksia, kuin jälkimmäisellä.

Vaikka sitä tässä ohimennen läpi käytiinkin, optimointi ei kuitenkaan kuulu tämän oppaan aihepiiriin, ja törmäystarkistuksissa voidaan tehdä myös monia muunlaisiakin optimointeja, samoin silmukoita voidaan toteuttaa myös muilla tavoilla. Tärkeämpää tässä tapauksessa on kuitenkin ymmärtää miten esitetyt silmukat toimivat. Ensimmäinen kahta For...Each:ia ja Esimerkki-typeä käyttävä oli aika selkeä, sama lista vain käydään läpi kahdesti kahden erillisen viitteen avulla. Viimeisessä listaa käydään läpi myös kahden viitemuuttujan avulla, mutta sisempi silmukka käy lävitse vain Esim2.Esimerkki = After(Esim.Esimerkki) -rivillä annetun Esim.Esimerkki-viitemuuttujan antaman instanssin JÄLKEISET instanssit listan loppuun saakka. Osannet tässä vaiheessa jo itse päätellä, miten toteuttaisit saman niin, että silmukka käy läpi kaikki instanssit ENNEN Esim.Esimerkki -instanssia listan alkuun saakka, jos moista sattuisit syystä tai toisesta tarvitsemaan.

Nyt on siis käyty läpi komentojen First, Last, After ja Before käyttöä listan läpikäynnissä. Kun ymmärrät miten nämä komennot toimivat, pystyt rakentamaan listojen läpikäyntiin juuri sellaisia silmukoita, joita kulloinkin tarvitset.


Tässä välissä esittelen pienen esimerkin, jossa näytetään ylläolevat törmäystarkistus-menetelmät käytännössä (käyttää jälleen CB:n omaa mediaa):

Code: Select all

Type Esimerkki
    Field Obj%  //Instanssin objekti
    Field HP%   //Instanssin kestopisteet (health)
End Type

//"Normi"alustukset
SCREEN 640, 480, 0, 1
Randomize Timer()

//Ladataan prototyyppi (master) -objekti, ja piilotetaan se
//Kaikki näkyvät objektit kloonataan tästä
Proto_Objekti = LoadObject("Media\guy.bmp", 36)
ShowObject Proto_Objekti, OFF


//Lasketaan ruudun keskipiste
RuutuKeskiX = ScreenWidth() / 2
RuutuKeskiY = ScreenHeight() / 2

//Luodaan 10 Esimerkki-typen instanssia
For i = 1 To 10    

    //Luodaan instanssi, ja otetaan sen viite Esim.Esimerkki-viitemuuttujaan
    Esim.Esimerkki = New(Esimerkki)

    //Asetetaan koordinaatit ympyrään ruudun keskipisteen ympärille
    X# = RuutuKeskiX - Cos(36 * i) * 100
    Y# = RuutuKeskiY - Sin(36 * i) * 100

    //Kopioidaan prototyyppi-objekti
    Esim\Obj% = CloneObject(Proto_Objekti)

    //Sijoitetaan instanssin objekti, tässä tapauksessa näyttökoordinaattien mukaan
    ScreenPositionObject Esim\Obj%, X#, Y#

    //Käännetään objekti osoittamaan näytön keskipisteeseen
    RotateObject Esim\Obj%, -36 * i
    
    //Asetetaan instanssin hp:t
    Esim\HP% = 10
    
Next i


//Kutsutaan pääsilmukkaa
PaaSilmukka()



//Tarkistetaan objektien väliset törmäykset
Function TarkistaTormaykset()
    //Käydään lista läpi
    For Esim.Esimerkki = Each Esimerkki
        //Käydään lista läpi uudelleen, tällä kertaa viitteet tallennetaan toiseen viitemuuttujaan
        For Esim2.Esimerkki = Each Esimerkki
            //Jos kyseessä ei ole sama instanssi...
            If Esim2 <> Esim Then
                //Tarkistetaan objektien törmäys
                If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then
                    //Jos Objektit törmäävät, vähennetään hp:tä molemmilta
                    Esim\HP% = Esim\HP% - 1
                    Esim2\HP% = Esim2\HP% - 1
                EndIf
            EndIf
        Next Esim2
    Next Esim
End Function

//Nopeampi ja parempi törmäystarkistus SAMANTYYPPISTEN instanssien välillä
Function TarkistaTormayksetOpt()
    //Käydään lista läpi
    For Esim.Esimerkki = Each Esimerkki
        //Haetaan Esim.Esimerkki-viiteosoittimen osoittaman instanssin jälkeen tuleva instanssi
        Esim2.Esimerkki = After(Esim.Esimerkki)
        //Suoritetaan sisäsilmukkaa (While...Wend) niin kauan, kuin Esim2.Esimerkki osoittaa johonkin instanssiin    
        While(Esim2 <> NULL)
            //Tarkistetaan törmäys
            If ObjectsOverlap(Esim\Obj%, Esim2\Obj%) Then
                //Jos Objektit törmäävät, vähennetään hp:tä molemmilta
                Esim\HP% = Esim\HP% - 1
                Esim2\HP% = Esim2\HP% - 1
            EndIf
                
            //Haetaan Esim2 -viitemuuttujaan sen nykyistä instanssia seuraava instanssi
            //Jos seuraava instanssi = NULL, While...Wend -silmukka päättyy
            Esim2.Esimerkki = After(Esim2.Esimerkki)
        Wend
    //Haetaan Esim.Esimerkki-viitemuuttujaan listan seuraava instanssi
    Next Esim
End Function


//Päivitetään objektien tilat
Function PaivitaObjektit()       
        
        //Käydään läpi instanssi-lista
        For Esim.Esimerkki = Each Esimerkki
            //Tarkistetaan onko instanssin HP nollassa tai alle?
            If Esim\HP% <= 0
                //Jos hp on nolla tai alle, tuhotaan objekti ja instanssi
                DeleteObject Esim\Obj%
                Delete Esim
                
            Else
                //Muutoin instanssilla on hp:itä jäljellä, liikutetaan sen objektia
                
                //Käännetään objektia sattumanvaraisesti, maksimissaan 5 astetta jompaan kumpaan suuntaan
                TurnObject Esim\Obj%, Rnd(-5, 5)
                //Liikutetaan objektia eteenpäin
                MoveObject Esim\Obj%, 1    

            EndIf
        Next Esim

        //Kutsutaan törmäystarkistusta
       //TarkistaTormaykset()
        
        //Kutsutaan parempaa törmäystarkistusta
        TarkistaTormayksetOpt()

End Function



Function PaaSilmukka()
    Repeat

        PaivitaObjektit()
                
        DrawScreen
        
    Until KeyDown(1)
End Function
PaivitaObjektit -funktiossa ovat kutsut törmäystarkistuksiin. TormaysTarkistus() -funktio käyttää kahta sisäkkäistä For...Each:ia, ja TormaysTarkistusOpt() -funktio esiteltyä tehokkaampaa metodia. Esimerkkikoodin funktiot eivät vastaanota mitään parametreja, joten tuon pystyy helposti muokkaamaan myös aliohjelmia käyttäväksi.

Osa 2B. Kehittyneempi typejen käsittely: ...listojen läpikäynti (jatkuu), Insert:n käyttö, typet funktioissa ja typet typeissä

Seuraavana vuorossa: Insert. Insert (verbi; työntää/pistää/panna/lisätä väliin/jonnekin) nimensä mukaisesti mahdollistaa instanssin paikan asettamisen listassa. Insert:iä yleensä After/Before/Last/First -komentojen tai näiden yhdistelmän kanssa.

Code: Select all

//Insert Instanssi-viite, Paikka
Insert Esim.Esimerkki, First(Esimerkki) //Asettaa Esim.Esimerkin osoittaman instanssin listan ensimmäiseksi
Insert Esim.Esimerkki, After(First(Esimerkki)) //Asettaa Esim.Esimerkin osoittaman instanssin listan toiseksi ensimmäiseksi
Insertiä tarvitsee harvemmin, esimerkkitapauksista tulisi mieleen lähinnä listan järjestäminen jonkin kentän arvon mukaisesti, esim. piirtojärjestys "syvyyssijainnin" mukaan, jos tehtäisiin jonkinlaista pseudo-3d:tä.

Insertin käyttöön liittyy joitakin vaaroja, esimerkiksi jos yritetään asettaa instanssi viimeisen jälkeiseksi [ After(Last(Esimerkki)) ], tai ensimmäistä edeltäväksi [ Before(First(Esimerkki)) ] päästään tutustumaan ihan uudenlaiseen virheilmoitukseen:
ERROR: Memory leak due to invalid type access attempt
Jos jokin halutaan asettaa listan ensimmäiseksi tai viimeiseksi, käytetään paikkana vain First(Esimerkki) tai Last(Esimerkki):iä, jossa Esimerkki -tyypin tilalle tulee tietysti halutun tyyppi-määritelmän nimi.

Jos insertillä jostain syystä (tosin en tiedä miksi joku näin tekisi) yritetään asettaa instanssi sen omalle paikalle:

Code: Select all

Insert Esim.Esimerkki, Esim.Esimerkki
saattaa koko instanssi kadota listasta, tai tulla MAV. Myös yritys asettaa listan viimeinen instanssi itsensä edelle näyttäisi jättävän tekemättä mitään (instanssi säilyy viimeisenä). Jos todella tarvitset Insert:iä johonkin, suosittelen ensin tekemään mahdollisimman yksinkertaisen testiohjelman, jolla kokeilla miten Insert käyttäytyy halutunkaltaisessa tilanteessa. Itselle ei nyt tule mieleen ainuttakaan tapausta, jossa Insert:iä olisin tarvinnut.

Ikävä kyllä, Insert:iä koskeva osuus oppaasta jää varsin suppeaksi. Toivottavasti joku, joka todella tätä tarvitsee, ja jaksaa kokeilla kaikki ns. "sudenkuopat" sen käytöstä läpi, vaivautuu myös kirjoittamaan jonkinlaisen oppaan aiheesta.



Jäljellä ovatkin enää kaksi viimeistä type-komentoa: ConvertToInteger ja ConvertToType. Nämä ovat todellista herkkua funktioita käyttäville, ja miksei muillekin.

Esitelläänpä komennot taas lyhyesti:


ConvertToInteger([Viitemuuttuja])
Tämä komento palauttaa viitteen osoittaman instanssin eräänlaisen "handlen" ("kahvan"), ts. kokonaisluvun, joka voidaan myöhemmin muuttaa takaisin viitemuuttujaksi ConvertToTypellä. (Myös CB:n kuvat ja objektit ovat siis tällaisia kokonaislukumuuttujiin tallentuvia "handleja", eli aiemmassa esimerkissä kun luotiin objekteja, niiden handlet tallentuivat typen Obj%-kenttään.)


Käyttöesimerkki:

Code: Select all

Handle% = ConvertToInteger(Esim.Esimerkki)    //Palauttaa kokonaisluvun, joka voidaan muuttaa takaisin
                //Esim.Esimerkin osoittaman instanssin viitteeksi ConvertToType:llä
Mikäs tässä nyt on niin mullistavaa? No tietysti se, että kokonaislukuja voidaan tallentaa mihin tahansa kokonaislukumuuttujaan: tavallisiin kokonaisluku-muuttujiin, jotka voivat tietysti olla myös globaaleja, jolloin handle on saatavilla kaikkialla, typejen kenttiin, taulukoihin, MemBlockeihin (vink vink)... Niitä voidaan myös syöttää normaaleina parametreina funktioihin, ja palauttaa funktioista.


ConvertToType([Kokonaisluku, joka sisältää ConvertToIntegeriltä saadun handlen])
Tämä komento muuttaa annetun handlen takaisin viitemuuttujaksi. On kuitenkin suositeltavaa tarkistaa, että saatu viite todellakin on kelvollinen (eli ei-NULL)!

Käyttöesimerkki:

Code: Select all

Esim.Esimerkki = ConvertToType(Handle)    //Palauttaa viitteen siihen instanssiin, josta Handle% 
                //saatiin ConvertToIntegerin avulla

//Tarkistetaan että viite on kelvollinen
//Tässä tapauksessa ohjelma heittää MakeError:lla virheen, mikäli näin ei ole
If(Esim.Esimerkki = NULL) Then MakeError("Epäkelpo handle!")               
ConvertToType on siis ConvertToInteger:in "vastafunktio", eli sillä voidaan muuttaa saatu handle takaisin type-instanssiviitteeksi.

Oma tapani on ollut laittaa typeihin ID% -kenttä, johon tallennetaan instanssin oma handle sen luonnin yhteydessä:

Code: Select all

Type JokinType
    Field ID%
End Type

//Luodaan uusi instanssi ja tallennetaan viite
Joku.JokinType = New(JokinType)

//Otetaan viitteestä handle typen omaan ID% -kenttään
Joku\ID% = ConvertToInteger(Joku.JokinType)
Ja mitäs hyötyä tästä taas olikaan? Kun viite instanssiin on jo olemassa, ei tarvitse joka kerta hakea ConvertToIntegerillä handlea uudestaan, vaan esimerkiksi funktiokutsujen yhteydessä voidaan käyttää instanssin oman kentän sisältöä suoraan:

Code: Select all

//Oletetaan että Joku.JokinType osoittaa tässä jotain instanssia, joka halutaan välittää funktiolle
JokinFunktio(Joku\ID%)
Funktion päässä taas handle:sta kaivetaan viite uudelleen esille:

Code: Select all

//Funktio joka ei oikeastaan tee mitään
Function JokinFunktio(Handle%)
    
    //Haetaan viite handlella
    Joku.JokinType = ConvertToType(Handle%)
    
    //Tarkistetaan viitteen kelpoisuus
    If(Joku.JokinType = NULL) Then
        //Lisätään vaikkapa näytölle ilmoitus virheestä:
        AddText("Funktiossa JokinFunktio havaittiin virheellinen handle!")
        
        //Palautetaan virhekoodi
        Return -1   //Tämä funktio palauttaa vain kokonaislukuja, on ohjelmoijasta kiinni mitä, jos yleensä mitään
                    //palautetulla arvolla tehdään
    EndIf
    
    //Kaikki ok, palautetaan 1
    Return 1
    
    //Funktion ei ole pakko palauttaa mitään, se palauttaa aina 0, jos se pääsee End Function -riville saakka
    //Funktiosta voidaan palata myös ennenaikaisesti pelkällä Return [arvo] -komennolla mistä tahansa kohtaa funktiota
    
End Function

Funktio siis ottaa kokonaisluku-parametrin, joka muutetaan funktion sisällä viitteeksi, ja viitteen oikeellisuus tarkistetaan. Syy miksi näin täytyy tehdä on se, ettei viitteitä voida välittää funktioille sellaisinaan, eikä viitemuuttujista voida tehdä globaaleita, niin että ne näkyisivät funktioiden "sisälle". Taulukot ja globaalit muuttujat "näkyvät" (ts. voidaan käyttää normaalisti) funktioiden sisällä.

Samaan tapaan funktion sisällä voidaan muuttaa viite handleksi ConvertToInteger:illä, ja palauttaa saatu handle, joka taas toisessa päässä muutetaan takaisin viitteeksi:

Code: Select all


//Kutsutaan funktiota, joka luo uuden instanssin ja palauttaa sen handlen
Handle% = JokuMuuFunktio()

Viite.JokinType = ConvertToType(Handle%)

//Tarkistetaan viite
If(Viite = NULL) Then MakeError("Epäkelpo handle")

//Funktio luo uuden instanssin ja palauttaa sen handlen
Function JokuMuuFunktio()
    
    //Luodaan uusi instanssi
    Viite.JokinType = New(JokinType)
    
    //Instanssiin voitaisiin asettaa jotain arvoja tässä
    //....
    
    //Haetaan handle
    Handle% = ConvertToInteger(Viite.JokinType)
    
    //Tallennetaan typen oma handle siihen itseensä (ei pakollista mutta kätevää)
    Viite\ID% = Handle%
    
    //Palautetaan handle
    Return Handle%
    
    //Sama voitaisiin kirjoittaa lyhyemmin:
    //Viite\ID% = ConvertToInteger(Viite.JokinType)
    //Return Viite\ID%  //Kentän sisältö voidaan palauttaa funktiosta, sehän on tässä tapauksessa vain kokonaisluku
    
End Function
Koska instanssit tallentuvat aina sisäiseen linkitettyyn listaan, voidaan niitä luoda myös funktioiden sisällä, eikä handlen palauttaminen ole välttämätöntä. Jos viitteitä kuitenkin halutaan välittää funktioille, on ainoa tapa (ainakin toistaiseksi) handlejen käyttö, joko parametrien kautta, taulukoilla tai globaaleilla muuttujilla. Henkilökohtaisesti suosin nimenomaan parametreja, mutta ei muissakaan keinoissa mitään vikaa ole. Viite kannattaa aina tarkistaa muutoksen jälkeen, jos vaikka oletkin välittänyt väärän arvon funktiolle. Myös Insert SAATTAA vaikuttaa handleihin, varma en kuitenkaan ole.

Jos instanssiin tarvitsee tallentaa useampia handleja (vaikkapa kaikki esineet toteutettaisiin typeinä, ja hahmoille tallennettaisiin handlet niiden poimimiin esineisiin), kannattaa tutustua MemBlockien käyttöön. Näillä voidaan myös toteuttaa muunkinlaisia muuttuvankokoisia taulukoita instanssien sisään, mutta käyttö vaatii jonkin verran harjoittelua. MemBlockit ovat nimensä mukaisesti "muistinpalasia", joihin voidaan kirjoittaa arvoja, mutta näiden kanssa tulee helposti virheitä. Tekemällä oppii, ja CB:n manuaalikin on harvinaisen kattava. Suosittelen lämpimästi lukemaan myös sieltä tässä oppaassa esitetyistä asioista.


CB:n oman manuaalin termistö eroaa tässä käyttämästäni, tässä vastaavuudet pääpiirteittäin:

  • Tämä opas <=> CB:n manuaali
  • Lista, Linkitetty lista <=> Kokoelma, joskus myös "taulukko", joskus "lista"
  • Instanssi,Esiintymä <=> Jäsen
  • Viite, -muuttuja, -tunnus <=> Osoitin, Tyyppimuuttuja, joskus myös pelkkä "muuttuja"
  • Kenttä <=> Muuttuja
  • Type-määritelmä <=> Kokoelma, Tyyppi
  • Handle <=> Osoitin (Toisessa paikkaa viitettä sanotaan osoittimeksi, mene ja tiedä... ;) )
Tässä tämä nyt taas toistaiseksi, seuraavassa ja samalla viimeisessä osassa keskitytäänkin jo sitten varsinaiseen peliesimerkkiin, joka käyttää funktioita, handleja, törmäystarkistuksia sisäkkäisillä silmukoilla ja luultavasti jonkinlaista MemBlock-systeemiä joidenkin handlejen talletukseen (käytin vastaavaa jo SuttuPrötössä, joten pääsen kierrättämään koodia vaivojeni säästelemiseksi :rolleyes: ). Idea on vasta hiljalleen muotoutumassa pääkopassani, mutta koitan saada vähän kaikkea mahdollista mukaan (kuitenkin niin, ettei joka funktiossa ole valtavasti kikkailua, vaan pyrin tekemään erillisiä funktioita, jotka esittelevät yhden tai kahden "tempun" hyödyntämistä). Aikataulusta en uskalla mitään sanoa, jos hyvin käy, ennen ensi viikkoa, jos ei, niin voi vierähtää pidempäänkin (koulu alkaa taas 8. päivä).

Näiden kahden osan perusteella luulen jo valtaosan oppaat kunnolla opiskelleiden pystyvän toteuttamaan omia projektejaan typejen kanssa. Pelkillä New-, Delete- ja For...Each -komennoilla pääsee hyvin pitkälle, mutta tässä osassa esitellyt keinot laajentavat typejen käyttötarkoituksia huomattavasti, ja suosittelenkin niiden kokeilua. Tietyissä asioissa taulukot ovat edelleen parempia (Esim. kartat ja vastaavat jo valmiiksi "taulukkomaiset" asiat), mutta typejen etuja tietyissä asioissa (vihollis- ja ammus-listat yms) ei voi kieltää.

Re: Arkistojen kätköistä: Type-esimerkki

Posted: Fri May 08, 2009 9:09 pm
by ezbe
Edit 19.10.2011: Korjattu linkki esimerkkipeliin

Osa 3. Typejen käyttöä käytännön peliesimerkissä

Tässä osassa käsitellään käytännön peliesimerkkiä, jossa on hyödynnetty typejen käyttöä mahdollisimman paljon. Edellisissä osissa esiteltyjä keinoja on pyritty hyödyntämään, ja koodi on kommentoitu mielestäni suhteellisen selkeästi. Oletan myös, että olet tässä vaiheessa lukenut jo molemmat aiemmat osat tarkasti läpi, tai tunnet muuten typejen toiminnan ja käytön hyvin!

Peliohjelma käyttää Ari Feldmanin vapaaseen käyttöön julkaisemia grafiikoita (SpriteLib Vol 1), jotka saa esim. täältä:
http://www.molotov.nu/?page=graphics#Spritelib

Muokkasin hieman alkuperäisiä grafiikoita (lähinnä poistelin tyhjää tilaa tilejen ympäriltä jne) ja leikkasin osan erilleen, koska kaikkia ei tarvittu. Mukana tulee SPRITLIB.TXT, jossa kehotetaan rekisteröitymään Feldmanin sivuille, ikävä kyllä ko. sivustoa ei ilmeisesti enää ole olemassa.

Koodi on omaani, lukuunottamatta objektien sijoitusta data-layerin tilejen sijainnin mukaan, johon otin mallia Jafnan koodiesimerkistä:
http://www.cbkk.arkku.net/koodi.php?id=83

Tilekartan tekoon on käytetty Astigman tekemää Astigmatoria, joka on aivan loistava tilekartta-editori, suosittelen lämpimästi:
http://www.coolbasic.com/forums/index.php?showtopic=823


Ja sitten itse asiaan...

Alkuperäinen suunnitelmani oli tehdä yksinkertainen topdown-shooter, mutta en sitä kuitenkaan tehnyt koska 1) alan olla kurkkuani myöten täynnä TapanKaikki-klooneja ja 2) koska en löytänyt riittävästi aiheeseen sopivaa vapaasti käytettävää mediaa. Ilmaisia grafiikoita sisältäviä sivuja selatessani törmäsin Feldmanin SpriteLibiin ja uusi idea alkoi hahmottua. Saanen esitellä:



HypiPompi! (klikkaa) EDIT 19.10.2011: LINKKI TOIMII TAAS



Pelinä esimerkki ei kovin kummoinen ole, mutta sen ympärille pystyisi kyllä sellaisen rakentamaan. Toivon kuitenkin etten tule törmäämään täällä tai muualla projekteihin, joissa on vaihdettu osa grafiikoista, lisätty yksi ominaisuus ja isketty "MADE BY SEP1"-tekstiä päälle. Koodia saa toki käyttää pohjana omissa projekteissa, mutta pelinä se kaipaa vielä paljon lisää ominaisuuksia ja hiontaa. Myöskin pelin krediiteissä tahtoisin tulla mainituksi esim. Thanks to -listassa, tämä ei kuitenkaan aivan välttämätöntä ole.

Ensimmäisenä suosittelen lataamaan paketin, avaamaan HypiPompi.cb -tiedoston CB:n editorissa, ja ajamaan koodin. Kontrollit ovat nuoli ylös, vasemmalle ja oikealle ja viholliset saa hengiltä Mario-tyyliin niiden päälle hyppäämällä.

Pidä paketti tallessa, jos alat muuttelemaan koodia. Mikäli saat ohjelman sellaiseen tilaan, ettei sitä enää voi ajaa/se kaatuu jatkuvasti etkä tiedä syytä, voit aina purkaa alkuperäiset tiedostot paketista. Kannattaa aloittaa kohdista, joiden toiminnan varmasti ymmärtää ja kokeilla tehdä niihin pieniä muutoksia. Pienenkin koodinpätkän lisäyksen jälkeen on suositeltavaa ajaa ohjelma, ja testata että juuri lisäämäsi asia varmasti toimii niin kuin pitää. Mikäli koodista on kysymyksiä, voit postata niitä tähän threadiin, mutta oletan että ainakin funktioiden toiminta ja käyttö on ennestään tuttua, samoin kuin perussilmukoiden ja muuttujien. Jos tuntuu ettet käsitä yhtään mitään koodista, lue CB:n manuaalia. Typejen käyttö on verrattain hankalaa, ja vielä vaikeampaa se on, mikäli ohjelmoinnin perusteetkin ovat vielä hakusessa.

Ohjelma on jaettu erillisiin moduuleihin (koodi-tiedostoihin), joista jokainen pyrkii käsittelemään vain tiettyä osaa pelistä (kartat, hahmot, esineet...). Jos useamman tiedoston auki pitäminen tuntuu hankalalta, koko pelin koodi löytyy myös paketista yhtenä pötkönä nimellä KaikkiKoodiYhdessä.cb. Henkilökohtaisesti pidän moduuleihin jakamista paljon selkeämpänä ja parempana tapana, mutta onnistuuhan tuo noinkin.

Funktiot on nimetty periaatteella <Moduuli>_<FunktionNimi>. Esimerkiksi Kartta_LataaKartta(File$, TileSet$) löytyy siis Kartta.cb -tiedostosta (jos käytät moduuleihin jaettua koodia), ja sitä kutsutaan esim näin:

Code: Select all

    //Ladataan kartta
    Kartta_LataaKartta("Level1.til", "TileSet.bmp")        


Poikkeuksena pelin päätiedostossa (HypiPompi.cb) EI ole käytetty moduulin nimeä funktioiden alussa.

Funktioiden nimet saattavat välillä olla hivenen pitkiä, tapanani on ollut nimetä funktiot mahdollisimman selkeästi, jotta käyttötarkoitus avautuisi jo pelkästä nimestä.

Kerättävistä esineistä ainoastaan sydän tekee jotain (pelaaja saa yhden osumapisteen lisää), muista vain lisätään instanssin handle hahmon Tavarat% -memblockiin, jonka avulla ne myös piirretään ruudun oikeaan ylälaitaan. Vaikka listassa on data-arvo kentän lopun merkkaamista varten, ja se on myös laitettu jo tilemappiin, ei koodi kuitenkaan käsittele tätä millään tavalla, ts. kenttää ei voi päästä läpi.

Vihollisten tekoäly ei päätä huimaa, mutta ajaa mielestäni asiansa oikein hyvin näin yksinkertaisessa esimerkissä. Ne on suhteellisen helppo huijata hyppäämään kielekkeeltä alas, eivätkä ne osaa seurata pelaajaa kovin hyvin.

Null-viitteistä paljastui kehityksen aikana ikävähkö piirre: olin vahingossa syöttänyt hahmon Obj%-kentän funktiolle handlen sisältävän ID%-kentän sijaan, ja peli kaatui MAV:iin, kun Obj-kentän arvon perusteella haetun viitteen läpi yritettiin käsitellä instanssin kenttiä. Ilmeisesti CB:n NULL-tarkistus ei aina tunnista onko viite sittenkään oikea, joten kannattaa olla tarkkana.

Saatan vielä muokata koodia, mikäli tarvetta esiintyy, mutta nyt jaossa oleva paketti on jo siis periaatteessa "valmis". Jos hirveitä mokia ja asiavirheitä tekstistä ja/tai koodista löytyy, ilmoittakaa minulle joko privaviestillä tai tässä threadissa, niin editoin niitä kuntoon. Toivottavasti joku tästä type-tutoriaalista ja -esimerkistä jotain saa irti, aikaa paloi allekirjoittaneelta ainakin kiitettävästi, tosin vapaaehtoistahan tämä oli. Hauskaa koodausta kaikille! B)


Koodin toiminta

Tämä selitys ei ole "täydellinen", eli en selosta aivan kaikkea pilkuntarkasti, vaan pääpiirteittäin koodin toiminnan. Jälkiviisasteluna huomasin koodia analysoidessa, että joitakin asioita olisi voinut tehdä paljonkin selkeämmin, mutten kuitenkaan alkanut muuttaa koodia paljoa alkuperäisestä.

Koodin toimintaselostusta lukiessa kannattaa seurata itse koodista missä ollaan menossa ja lukea myös kommentit. Omaakin koodia kannattaa kommentoida reippaasti, sillä pitemmän tauon jälkeen se helpottaa huomattavasti toiminnan hahmotusta.

Koodi käynnistyy HypiPompi.cb-tiedostosta. Alussa asetetaan grafiikkatila ja framelimit, jotta peli ei pyörisi liian nopeasti uudemmilla koneilla, ja estetään pelin sammuttaminen suoraan Esc:llä (SAFEEXIT OFF). Taustan tyhjennysväri asetetaan sinertäväksi.


Globaalit ja includet

Seuraavaksi määritellään joitain globaaleja, joita tarvitaan useassa eri paikassa pitkin ohjelmaa. Globaalien käyttöä ei useimmissa kielissä katsota hyvällä, mutta CB:ssä ne helpottavat joitakin asioita.



Globaaleissa muuttujissa ja vakioissa on CB:ssä jo ennestään tunnettu bugi. Jos muuttujan tai vakion perässä käytetään esittelyn jälkeen tietotyypin ilmaisevaa merkkiä (%, # tai $), CB ei käsittelekkään globaalia muuttujaa/vakiota vaan luo samannimisen paikallismuuttujan. Tässäkin kannattaa olla tarkkana.



Aiemmin (ennen tämän koodin toimintaselvityksen postausta) levityksessä olleessa koodissa oli turha SydanKuva -globaali, joka oli jäänyt vahingossa koodiin. Ennen kuin lisäsin enempiä esineitä peliin, käytin testauksessa pelkkää sydäntä, ja tein sille oman globaalin, joka unohtui koodiin.

Ensimmäinen globaali, Ukko_Protot, on taulukko johon ladataan pelin hahmojen animoidut objektit. Näitä objekteja sitten kloonaillaan (CloneObject) tarpeen mukaan, nämä ovat vain "prototyyppejä" (käytän termiä prototyypit kuvaamaan objekteja, jotka toimivat "master-objekteina" ja joista tehdään ainoastaan kopioita varsinaista käyttöä varten).

Toinen globaali, Esine_Protot, on myös taulukko, ja hahmo-prototyyppien tapaan näistäkin tehdään vain kopioita tarpeen mukaan. Kolmas globaali taulukko sisältää samat kuvat kuin Esine_Protot, mutta tällä kertaa imageina, jotta niitä voidaan piirtää ruutukoordinaatteihin (pelaajan healthit ja oikeassa yläreunassa näkyvät kerätyt esineet).

PelaajaHandle:een tallennetaan ConvertToIntegerillä saatava pelaajan PeliHahmo-instanssin handle. Näin päästään myöhemmin eri funktioissa helposti käsiksi suoraan pelaajan type-instanssiin.

Lopeta -globaalilla hallitaan pelisilmukasta poistumista, kun ollaan pelisilmukassa ja se asetetaan arvoon True, tipahtaa suoritus pois silmukasta (Repeat...Until Lopeta = True).

Tässä vaiheessa sisällytetään muut kooditiedostos eli moduulit: Kartta.cb, Kamera.cb, PeliHahmot.cb ja Esine.cb. Include -komento vastaa samaa kuin ottaisit ko. tiedoston kaiken sisällön ja kopioisit sen include-komennon sisältävään tiedostoon. Näin siis saadaan käyttöön muissa tiedostoissa olevat funktiot.


Pääsilmukka ja alustukset

Seuraavaksi kutsutaan pelin pääsilmukkaa (PaaSilmukka()), joka on tehty funktion sisään. Ohjelman suoritus siirtyy samaisessa HypiPompi.CB tiedostossa olevaan PaaSilmukka-funktioon. PaaSilmukka-funktiossa on ensimmäisenä kutsu Alusta-funktioon, joten suoritus siirtyy sinne. Alusta-funktio lataa pelin objektit ja tekee joitakin virheentarkistuksia, esim. puuttuvien tiedostojen varalta (Load<jotain> -komennot palauttavat 0, jos lataus epäonnistuu).

Ukko_Proto-taulukko täytetään animaatioilla eri Ukko<joku numero>.bmp -tiedostoista. For-silmukan käytön sijaan voisi vain komentaa peräkkäin

Code: Select all

Ukko_Proto(0) = LoadAnimObject("Ukko1.bmp", 17, 21, 0, 6)
Ukko_Proto(1) = LoadAnimObject("Ukko2.bmp", 17, 21, 0, 6)
Ukko_Proto(2) = LoadAnimObject("Ukko3.bmp", 17, 21, 0, 6)
ja tarkistaa jokaisen latauksen onnistumisen erikseen, mutta for-silmukan käyttö säästää vaivaa, ja omasta mielestäni näyttää selkeämmältä (makuasia). Ukko_Proto:t myös piilotetaan tässä, sillä näitähän käytetään vain varsinaisten pelihahmojen objektien kopioimiseen.

Esineiden lataus noudattaa pääpiirteittäin samaa kaavaa, mutta koska kaikki esineet ovat samassa tiedostossa, on niiden kanssa vähän kikkailtu luomalla jokaiselle oma image ja kopioimalla suuremmasta kuvasta ne yksi kerrallaan omiin imageihinsa. Tämän jälkeen on luotu vielä tyhjät objektit, jotka päällystetään irrotetulla kuvalla. Jos käytetty metodi vaikuttaa hankalalta, pääsee helpoimmalla kun erottaa kuvat omiksi tiedostoikseen, ja lataa ne yksi kerrallaan taulukon eri alkioihin. Funktion lopussa poistetaan vielä kopioinnin jälkeen turhaksi jäänyt väliaikaisimage (TempImage%), johon kaikki esineet sisältävä suuri kuva aluksi ladattiin.

Alusta-funktion päättyessä palataan sitä seuraavalle riville PaaSilmukka-funktioon. Tässä välissä voisi olla myös esimerkiksi alkudemo, ennen varsinaiseen pääsilmukkaan siirtymistä. Pääsilmukka alkaa "virallisesti" vasta Repeat-komennon kohdalta, ja sitä toistetaan kunnes globaalilla Lopeta-muuttujalla arvo on True saavuttaessa Until Lopeta = True-riville. Jälleen kutsutaan uutta funktiota, tällä kertaa varsinaista pelisilmukkaa (PeliSilmukka()).


Pelisilmukka: alustukset

Itse pelin pyörittäminen tapahtuu siis pääsilmukasta erillisen pelisilmukan sisällä. Ennen varsinaisen silmukan (Repeat...Until Lopeta = True) aloittamista ladataan kartta, tätä varten on tehty oma funktio Kartta.CB-tiedostoon, joka vastaanottaa parametreina kartan tiedostonimen ("Level1.til") ja tileset ("TileSet.bmp").

Funktion sisällä varmistetaan tiedostojen olemassaolo ja onko Kartta-typestä luotu jo instanssi (jos on, First(Kartta) palauttaa ensimmäisen Kartta-instanssin, jonka täytyy siis olla jotain muuta kuin NULL). Jos instanssi on jo luotu, sitä ei tuhota, vaan poistetaan aikaisemmin ladattu kartta (tilemap-objektin handle on tallennettu typen kenttään Kartta\Kartta%), asetetaan handle nollaksi (varmuuden vuoksi), ja tehdään sama myös tilesetille (Tilesetin handle on kentässä Kartta\TileSet%). Myös tilejen koot nollataan, jälleen varmuuden vuoksi. Jos kenttiin unohtuu arvoja, ja varsinaista type-instanssia ei kuitenkaan poisteta, saattavat tällaiset aiheuttaa myöhemmin hyvin vaikeasti jäljitettäviä bugeja.

Jos Kartta-instanssia ei vielä ollut, luodaan se tässä (else-lohko). Kartta-instanssin voisi tietysti aina tuhota tämän funktion sisällä, ja luoda uudelleen, tällöin tulee kuitenkin muistaa ENSIN poistaa jo olemassaolevat tilemap- ja tileset-objektit (Kartta\Kartta% ja Kartta\TileSet%), koska muutoin ne jäävät muistiin turhaan, eikä niihin enää pääse käsiksi koska handlet ovat kadonneet instanssin myötä. Kartta_Tuhoa()-funktio tekee juuri tämän, ja tässä riittäisikin kutsua ensin sitä, ja sitten luoda uusi Kartta-instanssi.

Tämän jälkeen meillä on käsissämme varmasti tyhjä tai tyhjennetty Kartta-instanssi, johon siis ladataan uusi tilemap ja tileset. Tässäkin kohtaa on vielä käytetty varmistuksia, jos esimerkiksi tilemap tai -set olisi korruptoitunut, olisi tiedoston olemassaolotarkistus (funktion alussa, If Not FileExists...) mennyt läpi, mutta latausfunktiot epäonnistuisivat, ja palauttaisivat handleksi 0. Tämänkaltainen tarkistelu voi tuntua turhalta, mutta se helpottaa huomattavasti ongelmien jäljitystä vikatilanteissa. Kun tilemap ja tileset on ladattu, lasketaan niistä kartan tilekoot ja reunat. Lopussa tutkitaan for-silmukoiden avulla kartan datakerroksesta missä pelaajan aloituspiste sijaitsee. Funktio päättyy tähän ja suoritus siirtyy takaisin pelisilmukkaan. Periaatteessa Kartta_SijoitaObjektit() -funktiotakin voitaisiin kutsua jo tässä, ennen pelisilmukkaan palaamista, koska sitä kutsutaan pelisilmukasta käsin aivan kohta.

Pelisilmukassa haetaan PAIKALLISEEN Kartta.Kartta-viitteeseen ensimmäinen Kartta-instanssia (kartta-järjestelmän ideana on, että Kartta-instansseja ei koskaan tulisi olla olemassa yhtä enempää samanaikaisesti). Kamera sijoitetaan niin, että Kartta-instanssiin tallennettu pelaajan aloituspaikka jää ruudun keskelle. Tämä saattaa sijaita niin, että osa kartan ulkopuolisestakin alueesta näkyy, mutta myöhemmin tehtävä kameran rajoitus estää pelaajaa huomaamasta tätä (koska kamera pakotetaan kentän rajojen sisälle ennen piirtoa, mutta tästä lisää myöhemmin). Seuraavana kutsutaan Kartta_SijoitaObjektit()-funktiota, jota siis olisi voitu kutsua jo suoraan Kartta_LataaKartta-funktiossa.

Kartta_SijoitaObjektit-funktion alussa haetaan taas ensimmäinen kartta-objekti, ja tarkistetaan että se varmasti saatiin. Varsinainen objektien ja esineiden sijoituksen toimintaperiaate muistuttaa aloituspaikan etsimistä, nyt vain tutkitaan eri arvoja datakerroksesta, ja jonkin määritellyn arvon löytyessä, sijoitetaan esimerkiksi pelihahmo tai esine tilen kohdalle. Jos arvoja tarvittaisiin myös muualla, data-kerrosten arvot kannattaisi itseasiassa määritellä vakioilla (Const) esimerkiksi Kartta.CB-tiedoston alussa, ja käyttää nimettyjä vakioita varsinaisten kirjoitettujen lukuarvojen (literaalien) sijaan, koska tällöin mahdollinen arvojen vaihtaminen myöhemmin tarvitsisi tehdä vain vakioihin.

Koska tässä funktiossa kutsutaan sekä PeliHahmot_LuoPeliHahmo()- ja Esine_LuoEsine()-funktioita, käyn molempien toiminnan tässä välissä läpi.

PeliHahmot_LuoPeliHahmo -funktiolle syötetään parametreinä pelihahmolle asetettava objekti (joka siis otetaan globaalista Ukko_Protot -taulukosta), ja sen haluttu aloitussijainti pelimaailmassa (siis maailma-, ei tilekoordinaatteina). Funktion alussa luodaan uusi instanssi, asetetaan sen Obj%-kenttään kopio syötetystä prototyypistä. Kloonatut objektit ovat aina oletuksena näkyviä, joten erillistä ShowObject-käskyä ei tarvita. Objekti sijoitetaan suoraan annettuihin koordinaatteihin, ja haetaan ConvertToIntegerillä juuri luodun Hahmo.PeliHahmo -instanssin handle, joka tallennetaan instanssin omaan ID%-kenttään. Tämän jälkeen handle tallennetaan vielä objektiin, ObjectInteger -komennolla, kerron törmäystarkistuksessa tarkemmin miten tätä hyödynnetään. Lopuksi palautetaan saatu handle, joka voidaan kutsuvassa koodissa ottaa talteen ja tarvittaessa muuntaa takaisin type-instanssin viitteeksi.

Esine_LuoEsine -funktiossa tehdään suurinpiirtein samat asiat, erona funktiolle annetaan vain halutun esineen numero, eikä varsinaista objekti-prototyyppiä, ja numero tallennetaan myös Esine-instanssiin. Tämän avulla tarkastetaan törmäyksissä, minkälainen esine oli kyseessä, ja sydämen tapauksessa lisätään pelaajan healthia, muutoin se lisätään pelaajan esineisiin.

Kun taas palataan takaisin pelisilmukkaan, on seuraavana vuorossa pelaajan luonti. Pelaaja luodaan saman PeliHahmot_LuoPeliHahmo-funktion avulla kuin vihollisetkin, nyt vain annetaan funktiolle eri prototyyppi, ja palautettu arvo (PeliHahmo-instanssin handle) tallennetaan globaaliin PelaajaHandle-muuttujaan. Huomaa että globaalin perässä EI saa käyttää muuttujatyypistä kertovaa merkkiä (tässä tapauksessa %), muutoin arvo tallentuu paikalliseen eikä globaaliin muuttujaan. Pelaaja tarvitsee lisäksi hieman muitakin arvoja, joten kutsutaan AlustaPelaaja() -funktiota näiden alustamiseksi.

AlustaPelaaja -funktiossa haetaan pelaajan instanssi juuri asetetun globaalin handlen avulla, ja tarkistetaan että instanssi myös saatiin. Viholliset kuolevat yhdestä osumasta, joten niiden HP-kentän arvolla ei ole merkitystä, mutta pelaaja kestää useampia osumia, joten hän tarvitsee jonkin aloitusarvon healthilleen. Hahmon tavarat säilytetään Memblockissa, joten sekin täytyy alustaa MakeMEMBlock-komennolla. CB:ssä ei onnistu tyhjän (0 tavun mittaisen) memblockin luonti, joten aluksi luodaan tavun pituinen memblock. Jälleen haetaan ensimmäinen Kartta-instanssi, ja Pelaaja sijoitetaan sinne merkittyihin aloituskoordinaatteihin. Koska esineet on jo tässä vaiheessa luotu, asetetaan niille törmäys pelaajan kanssa, muiden pelihahmojen kohdalla käytetään hieman toisenlaista törmäystarkistusta. AlustaPelaaja ei palauta mitään.


Pelisilmukka: Itse silmukka

Nyt kun palataan pelisilmukkaan, ollaan viimein varsinaisessa pelin pääsilmukassa. Repeat...Until Lopeta = True -rivien välistä koodia suoritetaan, kunnes globaali Lopeta-muuttuja on arvossa True saavuttaessa Until -riville. Ensimmäisenä silmukan aluksi kirjoitetaan fps-arvo ruudun vasempaan yläreunaan. Tästä voidaan seurata kuinka nopeasti peli pyörii. Varsinaiseen pelilogiikkaan liittyen ensimmäinen toiminto on lukea pelaajan kontrollit. Kutsutaan siis PelaajaKontrollit() -funktiota.

PelaajaKontrollit hakee taas aluksi globaalin handlen avulla pelaajan instanssin ja tarkistaa sen. Jos pelaaja sattuu olemaan köynnöksen kohdalla, tarkistetaan tämä GetMapin avulla, ja köynnösdatan löytyessä, asetetaan pystysuuntainen liike nollaksi. Köynnöksessä liikkuminen kuitenkin onnistuu, koska varsinaiset liikettä muuttavat napit tarkistetaan vasta tämän jälkeen. Näppäimet on määritelty "kiinteiksi", eli niitä ei voisi muuttaa esim. optionseista käsin, tämä täytyisi koodata hieman eri tavalla. Vasen- ja oikea-nuolinappi muuttavat pelaajan x-suuntaista liikettä, ylös-nuoli asettaa pelaajan y-suuntaisen liikkeen, mikäli pelaaja on maassa (eli y-liike = 0). Jos Esciä on painettu, asetetaan globaali Lopeta-muuttuja arvoon True, jolloin pelisilmukasta pudotaan pois Until-rivillä.

Pelisilmukassa kutsutaan seuraavaksi vihollisten tekoälyä hoitelevaa funktiota. Suoraan sanottuna en (ainakaan nyt) jaksa alkaa selittää miten tekoäly toimii, se on suhteellisen yksinkertainen ja vähänkin osaavampi ohjelmoija kyllä pystyy sen päättelemään jo koodia ja kommentteja tutkimalla. PeliHahmot_KannattaakoHypata() -funktiolla voidaan tarkistaa, päätyisikö hyppy johonkin kiinteälle tasolla nykyisellä nopeudella ja suunnalla.

Pelisilmukkaan paluun jälkeen mennään seuraavaksi suorittamaan varsinainen hahmojen liikuttaminen (tekoäly on vain "päätöksentekoa", eli ohjaa mitä eri hahmot tekevät seuraavaksi, varsinainen toiminta tapahtuu vasta täällä). Ensimmäinen Kartta-instanssi haetaan paikalliseen viitteeseen, ja käydään kaikki hahmot For...Each -silmukan avulla läpi. Hahmojen x-suuntaiset maksiminopeudet rajoitetaan, jotta ne eivät voisi liikkua liian nopeasti, ja niitä hidastetaan hiljalleen (jotta esim. pelaaja ei pysähdy kuin seinään, kun napin päästää irti). Jos nopeus on tietyn rajan alle (0.1), asetetaan se suoraan nollaan, koska muutoin hahmo liikkuisi vielä pitkään, mutta niin pienellä nopeudella ettei tämä näkyisi pelissä millään tavalla.

Hahmojen törmäykset kartan kanssa tarkistetaan tässä vaiheessa yksinkertaisesti tutkimalla hahmon jalkojen alla sijaitsevasta tilestä sen törmäysdataa. Jos törmäysdataa ei ole, aletaan y-suuntaista nopeuttaa korottaa, jolloin hahmo lähtee putoamaan. Y-nopeus rajataan taas tiettyyn arvoon, ettei hahmon putoaminen kiihdy loputtomiin. Jos alla on maata (else-lohko) ja hahmo on putoamassa alaspäin, asetetaan sen y-nopeus nollaan. Tässä kohtaa tehdään myös korjaus hahmon y-suuntaiseen sijaintiin, sillä hahmo saattaa pudota osittain tilen sisään, putoamisnopeudesta riippuen.

Jos hahmo liikkuu sivusuuntaisesti, ja se törmää johonkin tilemapin hit-kerroksessa, asetetaan se "kimpoamaan", eli x-suuntainen liike käännetään päinvastaiseksi ja hidastetaan puoleen (*0.5). Hahmojen animaatiot asetetaan hahmon liikenopeuksien mukaan, maassa olleessa ja sivuttain juostessa soitetaan juoksuanimaatiota eri suuntiin, hypätessä näytetään yhtä framea ja paikoillaan ollessa pysäytetään animaatio.

Vasta nyt tapahtuu varsinainen hahmon liikuttaminen (MoveObject) hahmon x- ja y-nopeuksien mukaan. Aikasemmin näitä arvoja on vain asetettu, mutta nyt hahmoa siis siirretään x-akselilla x-nopeuden mukaan ja y-akselilla y-nopeuden mukaan.

Viimeinen tarkistus liittyy kartalta pois putoamiseen, tässä vain tarkistetaan onko kyseessä pelaaja vai joku vihollisista. Jos kyseessä oli pelaaja, asetetaan Lopeta -arvo Trueksi, ja suoritus putoaa pelisilmukan päätteeksi ulos silmukasta, ja takaisin pääohjelmaan. Jos kyseessä oli joku muu pelihahmo, tyydytään vain poistamaan se PeliHahmot_TuhoaHahmo-funktion avulla, jolle syötetään hahmon handle Hahmo\ID% -kentästä.

Paluu pelisilmukkaan, vuorossa hahmojen törmäystarkistukset. Tässä ei käytetä tutoriaalissa esitettyä, kaikkien samassa listassa sijaitsevien hahmojen törmäystarkistusta, sillä viholliset eivät voi törmätä keskenään. Alussa otetaan siis totuttuun tapaan pelaajan handle, ja lähdetään For...Each -silmukalla tarkistamaan osuvatko muut hahmot pelaajaan. Ensimmäiseksi tarkistetaan, että kyseessä on jokin muu hahmo kuin pelaaja itse. Seuraava tarkistus tarkistaa onko hahmo niin lähellä, että se voisi edes törmätä pelaajaan. Jos on, tarkistetaan onko pelaajan y-suuntainen liike eri kuin 0 (eli onko pelaaja hyppäämässä/putoamassa), ja samalla onko pelaaja hahmoa ylempänä. Jos molemmat pitävät paikkansa osuvat pelaajan jalat hahmon päähän (suurinpiirtein), ja hahmo poistetaan.

Jos hahmo oli riittävän lähellä ja edellinen tarkistus ei pidä paikkaansa, on hahmo osunut pelaajaan. Jos pelaajan vahinkolaskuri on nolla, pelaajalta vähennetään yksi HP, ja vahinkolaskuri asetetaan 30:een. Samalla tarkistetaan vielä putosivatko pelaajan HP:t nollaan tai alle, ja jos näin kävi, peli päättyy.

Seuraavana pelisilmukassa on vuorossa kameraan liittyvät toiminnot. Kamera_SeuraaObjektia vain asettaa annetun PeliHahmo-instanssin ruudun keskelle CloneCameraPositionin avulla. Tämän jälkeen kutsuttu Kamera_RajoitaKamera() estää kameraa liikkumasta sijaintiin, jossa se näyttäisi tilekartan ulkopuolelle.

PeliSilmukassa kutsutaan jälleen uutta funktiota, TarkistaPelaajanEsineisiinTormaykset(). Tämä törmäystarkistus hyödyntää CB:n omaa törmäysjärjestelmää. Pelaajan instanssi haetaan globaalin handlen kautta ja tarkistetaan (duh?). CountCollisionilla pyydetään pelaajaan rekisteröityneitten törmäysten lukumäärä. Huomaa, että for-silmukkaan ei koskaan mennä, jos Tormayksia% -arvo on alle yksi. For-silmukan sisällä haetaan ensin törmäyksen vastapuoli (eli objekti) GetCollisionin ja törmäysindeksin avulla (for-silmukka käy kaikki törmäysindeksit läpi). Koska CB:n omaa törmäysjärjestelmää käytettiin ainoastaan pelaajan ja esineitten välillä, on kyseessä siis oltava esine.

Tässä kohtaa hyödynnetään ObjectIntegerillä luonnissa objektiin tallennettua handlea hakemalla se ObjectInteger-kutsulla ( Handle% = ObjectInteger(Obj%) ), ja muuttamalla handle takaisin Esine-instanssin viitteeksi ( Esine.Esine = ConvertToType(Handle) ). Oikea instanssi voitaisiin löytää myös esimerkiksi käymällä kaikki Esine-instanssit läpi, ja ottamalla se instanssi, jonka Obj% -kentän arvo on sama kuin GetCollisionista saatu, mutta ObjectIntegeriä hyödyntämällä ei tarvitse turhaan käydä listaa lävitse. Saatu viite tarkistetaan taas varmuuden vuoksi. Nyt otetaan käyttöön Esine-instansseihin tallennettu esineen nro. Tässä esimerkissä ainoastaan esine nro 1, eli sydän tekee jotain, kaikki muut vain tallennetaan pelaajan Tavarat%-kentän osoittamaan memblockiin. Jos kyseessä oli sydän, pelaajan HP:tä kasvatetaan yhdellä ja esine tuhotaan Esine_TuhoaEsine -funktiolla. Jos kyseessä oli jokin muu, piilotetaan Esine-instanssin objekti pelimaailmassa (jolloin myös sen törmäyksiä ei oteta huomioon), ja lisätään pelaajan Tavarat-kentän osoittamaan MemBlockiin esineen handle.

Taas paluu pelisilmukkaan, ja nyt ollaankin jo loppusuoralla: jäljellä on enää tietojen piirto ruudulle ja ruudun varsinainen piirto. PiirraPelaajanStatus() -funktiossa haetaan jälleen kerran pelaajan instanssi ja tarkistetaan se. Vasempaan yläkulmaan piirretään pelaajan hp:n verran sydämiä, piirtopaikkaa siirretään joka silmukalla hieman oikealle, ja korkeutta säädetään sin-funktion palauttaman arvon avulla.

Esineiden piirtoa varten tarvitsee ensin selvittää, montako esinettä pelaajalla on. Tämä selviää tutkimalla kuinka suuri Tavarat-kentän osoittama memblock on, ja jakamalla tulos neljällä (yksi handle on 4-tavuinen kokonaisluku). Piirron aloitussijainniksi asetetaan 400, 20, ja for -silmukkaa toistetaan esineiden määrän verran. Ensimmäisenä silmukan sisällä haetaan esineen handle memblockista, muutetaan se viitteeksi ja tarkistetaan viite. Esineen Nro-kentän avulla haetaan oikea image Esine_Imaget -taulukosta, ja piirretään se nykyiseen sijaintiin. X-sijainti korotetaan tietyn verran (20 pikseliä), ja jos x-sijainti on yli tietyn arvon ( > 540), palautetaan x-sijainti aloitussijaintiin (400) ja kasvatetaan y-sijaintia niin, että esineet menevät eri "riveille". Lopuksi tarkistetaan hahmon vahinkolaskuri, ja jos se on yli 0, vähennetään siitä yksi. Mod-komennon avulla tarkistetaan onko laskurin arvo parillinen vai pariton. Parittomilla arvoilla hahmo piilotetaan ja parillisilla näytetään, jolloin hahmo välkkyy niin kauan kunnes vahinkolaskurin arvo on 0. Kuten hahmojen törmäystarkistuksesta muistetaan, ei pelaaja voi vahingoittua niin kauan kuin vahinkolaskuri on yli nolla, ja välkkymisellä tästä viestitetään pelaajalle.


Pelisilmukka: Silmukasta poistuminen

Lopuksi pelisilmukkaan palattua piirretään ruutu (DrawScreen) ja saavutaan Until-riville. Jos pelaaja on kuollut tai painanut pelisilmukan suorituksen aikana Esciä, on Lopeta -muuttujan arvoksi asetettu True, ja pelisilmukan suoritus loppuu. Seuraaville riveillä kutsutaan erinäisiä tuhoamisfunktioita, jotka tuhoavat pelaajan (tuhottava erikseen, koska pelaajalla on memblock joka pitää muistaa vapauttaa muistivuotojen välttämiseksi), kartan, kaikki muut pelihahmot ja esineet. Jos pelihahmojen tuhoamista kutsuttaisiin ennen pelaajan tuhoamista, tuhoutuisi pelaaja muiden pelihahmojen mukana, eikä memblockia vapautettaisi. Vaikka memblock missään vaiheessa tuskin montaa tavua muistia syö, on tällaiset asiat hyvä opetella tekemään. Jos kyseessä olisi vaikka suuri kuva, voisi muistivuoto syödä joka kerralla jopa useita megatavuja! Prototyyppejä ei tuhota missään vaiheessa ohjelman suorituksen aikana, vaan niiden annetaan vapautua ohjelman suorituksen loppuessa. Lopeta-arvo nollataan, jotta ei samantien pudota pois myös pääsilmukasta, johon nyt siis palattaisiin.

Kun suoritus putoaa pelisilmukasta takaisin pääsilmukkaan, esitetään pelaajalle kysymys pelin jatkamisesta. While-silmukalla toistetaan napin kyselyä, kunnes arvo on joko k tai e, ja jos arvo on e, asetetaan Lopeta-muuttuja taas Trueksi, jolloin tiputaan samantien myös pääsilmukasta pois. Paasilmukka-funktiosta palatessa palataan aivan ohjelman alkuun, PaaSilmukka() -kutsun jälkeiselle riville. Tässä olisi hyvä olla End, jolloin ohjelman suoritus varmasti loppuu, eikä pääse jatkamaan matkaansa muualle koodiin. Tässä tapauksessa Endiä ei ole, mutta koska kaikki koodi HypiPompi.CB-tiedostossa tämän rivin jälkeen on funktioissa, päättyy ohjelman suoritus (funktioiden sisälle voi päästä ainoastaan kutsumalla niitä).




Toivottavasti tämä pikainen selitys selvitti ohjelman toimintaa niin, että pääset muokkaamaan sitä. Ohjelman osia saa käyttää vapaasti miten parhaaksi katsoo, eikä tämän ideana ole esittää mitään absoluuttisen oikeaa ja parasta tapaa tehdä pelejä, vaan esitellä lyhyesti miten typejä voi pelissä hyödyntää.

Re: Arkistojen kätköistä: Type-esimerkki

Posted: Fri May 08, 2009 9:43 pm
by kaneli2000
Omfg, oli hieno tuto. Laitan itselleni Wordilla johonki ettei huku, kun tätä tarvitsen.

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Fri May 08, 2009 10:21 pm
by Awaclus
Kiitos!!!

Tätä en itse tosin tarvitse, mutta luulen, että tulen tähän linkittämään useinkin.

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Fri May 08, 2009 11:26 pm
by mikeful
All hail Ezbe.

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Fri May 08, 2009 11:37 pm
by phons
Näin etusivulta, että täällä on uusi tyyppi: ezbe. Ensimmäinen ajatus: Taas spämmijöitä!!!!!!!
Tämän perusteella unohdin nuo heti! Tällaista en ole enne nähnyt 3 viestiä lähettäneeltä. :P
Todella hyödyllinen, ja opin itsekkin jotain noista After ja First jutuista, vaikken kokonaan noita lukenutkaan. Lainaan vielä mikefulia: All hail ezbe.

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Sun May 10, 2009 2:33 pm
by MaGetzUb
phons wrote:Näin etusivulta, että täällä on uusi tyyppi: ezbe. Ensimmäinen ajatus: Taas spämmijöitä!!!!!!!
Tämän perusteella unohdin nuo heti! Tällaista en ole enne nähnyt 3 viestiä lähettäneeltä. :P
Todella hyödyllinen, ja opin itsekkin jotain noista After ja First jutuista, vaikken kokonaan noita lukenutkaan. Lainaan vielä mikefulia: All hail ezbe.
Offtopic: Lol, Ezbe oli vanhan foorumin legendaarisimpia jätkiä, hänhän teki SuttuPrötön objekti pohjaisesta partikkelisysteemistä Stand Alonen..


On muute ketun hyvä tuto! Gj!

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Sun May 10, 2009 6:27 pm
by Nasse-setä
Tulee tarpeeseen. Itse juuri tuskailin näiden tyyppien kanssa, kunnes löysin tämän.

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Sun May 24, 2009 10:01 am
by skorpioni-cb
All hail ezbe. On hyvä tutoriali ImageImage

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Fri Nov 20, 2009 10:33 am
by qwetyone
Joo tästä oli todella paljon apua, kiitos!

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Sat Jan 23, 2010 6:09 pm
by 85 spider
Tosta oli paljon apua. Oli tosi hyvä :D :D :D

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Sat Mar 27, 2010 7:36 pm
by Warlock
Oot mun jumala ezbe, ja mun valaistukseni on typejen käytön parempi oppiminen.

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Fri Mar 04, 2011 8:41 pm
by TheDuck
HypiPompin latauslinkki ei toimi.

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Wed Oct 19, 2011 9:52 pm
by ezbe
TheDuck wrote:HypiPompin latauslinkki ei toimi.
Korjattu, kaverin palvelimen hosti vaihtui joskus... vuosi sitten? =)

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Fri Mar 09, 2012 10:11 am
by PunCrathod
Tulipa pitkästä aikaa kirjoiteltua taas CB koodia ja törmäsin niin omituiseen "ominaisuuteen" että vaivauduin jopa tännekkin kirjoittelemaan asiasta.

Code: Select all

Type opennodes
    Field opnode
EndType

onode.opennodes=New(opennodes)
onode\opnode=0
onodeb.opennodes=First(opennodes)
onodeb=onode
onodeb\opnode=1
onodec.opennodes=First(opennodes)
Delete onode
Print onode\opnode
onode\opnode=2
Print onode\opnode
onodec.opennodes=First(opennodes)
onodec\opnode=3
Print onode\opnode
Delete onodeb
WaitKey

Tämä saattaa olla taas näitä järjestelmä riippuvaisia "ominaisuuksia" mutta itselläni ainakin tuon koodin ajaessa tulostuu

Code: Select all

1
2
jonka jälkeen tulee MAV. Ja tämäkös minua hämmentää. Voisiko joku asiasta enemmän tietävä informoida että mitä ihmettä. Tarkennukseksi vielä se ettei tunnu olevan mitään väliä minkä instanssin käskee poistetuksi ja silti ne kaikki ovat olemassa. Ja jos yrittää poistaa toista instanssia niin tulee vain se kuuluisa MAV.

Tämä ominaisuus nyt kovasti häiritsee projektiani jossa pitäisi nimenomaan poistaa vain ja ainoastaan kohdetta lähin node joka ei Delete komennosta huolimatta poistu.

Re: Arkistojen kätköistä: Type-tutoriaali

Posted: Fri Mar 09, 2012 11:33 am
by Latexi95
PunCrathod wrote:Tulipa pitkästä aikaa kirjoiteltua taas CB koodia ja törmäsin niin omituiseen "ominaisuuteen" että vaivauduin jopa tännekkin kirjoittelemaan asiasta.

Code: Select all

Type opennodes
    Field opnode
EndType

onode.opennodes=New(opennodes)
onode\opnode=0
onodeb.opennodes=First(opennodes)
onodeb=onode
onodeb\opnode=1
onodec.opennodes=First(opennodes)
Delete onode
Print onode\opnode
onode\opnode=2
Print onode\opnode
onodec.opennodes=First(opennodes)
onodec\opnode=3
Print onode\opnode
Delete onodeb
WaitKey

Tämä saattaa olla taas näitä järjestelmä riippuvaisia "ominaisuuksia" mutta itselläni ainakin tuon koodin ajaessa tulostuu

Code: Select all

1
2
jonka jälkeen tulee MAV. Ja tämäkös minua hämmentää. Voisiko joku asiasta enemmän tietävä informoida että mitä ihmettä. Tarkennukseksi vielä se ettei tunnu olevan mitään väliä minkä instanssin käskee poistetuksi ja silti ne kaikki ovat olemassa. Ja jos yrittää poistaa toista instanssia niin tulee vain se kuuluisa MAV.

Tämä ominaisuus nyt kovasti häiritsee projektiani jossa pitäisi nimenomaan poistaa vain ja ainoastaan kohdetta lähin node joka ei Delete komennosta huolimatta poistu.
Ainakin koitat käyttää tyyppi osoitinta jonka juuri olet poistanut ja se todennäköisesti aiheuttaa MAVIn. Kokeileppas For-Each loopin avulla tutkia onko se todellakin poistunut (todennäköisesti on). Tuo on huono tapa tutkia, onko joku oikeasti poistunut, koska MAV ei välttämättä tule aivan täsmälleen silloin, kun virhe tapahtuu. Tyyppiosoitin ei häviä minnekkään vaikka poistat sen Deletellä. Se osoittaa vain johonkin, missä ei enää ole mitään, mutta se poistaa tyypin jäsenen listasta.

Code: Select all

Type opennodes
    Field opnode
EndType

onode.opennodes=New(opennodes)
onode\opnode=0
onodeb.opennodes=First(opennodes)
onodeb=onode
onodeb\opnode=1
onodec.opennodes=First(opennodes)
Delete onode //Poistat
Print onode\opnode //Koitat käyttää jo poistettua
onode\opnode=2 //Ja taas
Print onode\opnode //Ja vielä kerran
onodec.opennodes=First(opennodes)
onodec\opnode=3
Print onode\opnode
Delete onodeb
WaitKey