Game Kernel ja Player's Interface Module (pelimoduuli) muodostavat kokonaisuuden, joka vastaa varsinaisen pelin kulusta. Pelimoduulin tehtäviin kuuluvat pääasiassa pelaajan valintojen (=input) tulkinta ja suorittaminen sekä pelisivujen (=output) muodostus. Lisäksi moduulin vastuulla on näitä tukevia toimintoja, kuten uuden seikkailun valintatilanne ja erilaiset siirtymistoiminnot.
Tyypillinen pelikierros on tapahtumasarja, jonka voisi kuvata seuraavasti:
Kaikilla WWW-palvelimen kutsumilla proseduureilla on ainakin kaksi argumenttia: UserID ja Round. Nämä mahdollistavat sekä käyttäjän identifioinnin että käyttöoikeushallinnan. Tarkemmin niistä kerrotaan käyttöoikeushallintaa käsittelevässä luvussa.
ActionDo- ja CreateLocation-proseduureista on myös ActionDoDirect- ja CreateLocationDirect-versiot, jotka voidaan suorittaa suoraan kutsuna toisesta proseduurista. Erilaisia käyttöoikeus ym. tarkistuksia ei tällöin suoriteta. Näiden proseduurien parametreissa Round korvautuu NextRound-parametrilla, mikä on seuraavaa pelikierrosta varten luotu käyttöoikeustunnus.
Moduulin sisäiset tietovuot.
PostScript-versio:
Pelin käyttöliittymä ei ole parseriin perustuva, kuten seikkailupeleissä tavallisesti, vaan kiinteisiin, etukäteen suunniteltuihin toimintoihin perustuva. Toiminnot näkyvät käyttäjälle tavallisina WWW hypertekstilinkkeinä. Koska kaikkia mahdollisia toimintoja ei välttämättä haluta näyttää käyttäjälle (se voisi paljastaa juonta etukäteen), täytyy mahdolliset toiminnot valikoida etukäteen, ennen kuin sivu näytetään käyttäjälle. Tästä aiheutuu mm. se, että toiminnon ehtoja ei tarvitse jälkeenpäin tarkistaa. Tietokannan rasitus on suurempi, sillä kaikkien toimintojen ehdot täytyy käydä läpi etukäteen. Toisaalta pelaajalle siirrettävän tiedon määrä hiukan vähenee.
Kesken jääneen pelin pelaaminen alkaa siten, että esim. log in -rutiini kutsuu CreateLocation- tai CreateLocationDirect-proseduuria. Tämä luo pelaajan pelitilannetta kuvaavan paikkasivun, jolle se asettaa tekoja vastaavat hypertekstilinkit. Hypertekstilinkit kutsuvat niitä käytettäessä ActionStart-proseduuria, joka saa tiedon siitä, minkä teon käyttäjä valitsi.
Useissa proseduureissa tarvitaan kaksi eri aloituskohtaa riippuen siitä, kutsuuko niitä WWW-palvelin vai kutsutaanko niitä toisesta proseduurista (...Direct). Tämä aiheutuu siitä, että WWW-palvelimelta tulevan kutsun jälkeen tarvitsee suorittaa käyttövaltuutustarkistukset.
WWW-ympäristössä käyttäjän toimenpide joko lataa tiedoston tai käynnistää ohjelman. Tämä eroaa merkittävästi perinteisestä ohjelmointiympäristöstä siinä mielessä, että ohjelma ei voi jäädä odottamaan käyttäjän vastausta, sillä ainoa vastaus on, että WWW-palvelin käynnistää toisen ohjelman, tai toisen instanssin samasta ohjelmasta! WWW-palvelin käynnistämä ohjelma saa argumenteikseen käyttäjän syöttämän tiedon, ja ohjelman pitää lähettää vastaus käyttäjälle tulostamalla HTML-teksti stdout- eli standarditulostusvirtaan. WWW-palvelin lähettää vastauksen käyttäjälle todennäköisesti vasta kun ohjelma on päättynyt. Tämä aiheuttaa sen, että muuttujia ei voida käyttää normaalilla tavalla, vaan kaikki tieto on tallennettava tietokantaan tai lähetettävä HTML-sivun mukana, jolloin se kiertää takaisin ohjelmalle käyttäjän browserin kautta. LAG:ta varten on valittu käytäntö, että mahdollisimman paljon tietoa tallennetaan tietokantaan.
Jos pelaajalla ei ole mitään seikkailua kesken (jos hän ei ole koskaan aloittanutkaan, tai jos hän on juuri selvittänyt seikkailun), siirtää pelin paikkasivun luova proseduuri pelaajan seikkailuvalintasivulle. Seikkailuvalintasivulla pelaaja saa listan olemassa olevista seikkailuista, joista hän voi valita haluamansa. Pelaajan valittua OK-napin, suorittaa järjestelmä seikkailun alustuksen, jonka jälkeen pelaaja siirretään seikkailun alkupaikkaan.
Seikkailunvalintasivu sisältää yksinkertaisen valintalistatyyppisen kentän, josta pelaaja valitsee haluamansa seikkailun. Sivulla olevasta OK-napista käynnistyy AdventureInitialization-proseduuri. Proseduurin päätehtävät ovat:
Näiden jälkeen proseduuri käynnistää proseduurin, joka luo pelisivun käyttäjän nykyisestä paikasta (kuvattu tarkasti myöhemmin tässä luvussa).
Tekoihin liittyvien ehtojen avulla saadaan seikkailusta mielenkiintoinen ja vaikea. Ilman niitä olisi pelin kulku lineaarista eikä erityisen kiehtovaa. Koska LAG:ssa myöskin liikkuminen paikasta toiseen on "teko", on sekin helposti ehdollistettavissa. Yhdelle teolle voidaan määritellä haluttu määrä perusehtoja. Teon perusehdot yhdistetään toisiinsa JA-periaatteella. Jos tarvitaan TAI-käsittelyä, esim. voit ostaa jotain, jos sinulla on rahaa TAI pankkikortti, voidaan luoda kaksi erillistä tekoa, esimerkkitapauksessa "osta rahalla" ja "osta pankkikortilla". Jos toinen ehto täyttyy, vain toinen on mahdollinen, jos kummatkin ehdot täyttyvät, pelaaja voi valita kumman hyvänsä.
LAG:n tukemat perusehdot ensimmäisessä versiossa ovat:
Ehdot on numerokoodattu ActionConditions-tauluun. Ehtojen parametrit ja niitä vastaavat tietokannan kentät sekä toiminnan kuvaus ovat vastaavasti seuraavat:
Pelin kirjoittaja voi määritellä teon aiheuttamaan halutun määrän alkeistapahtumia. Tämän etuna on se, että tekojen seurausvaikutukset voivat olla niin monimutkaisia, kuin halutaan. Esimerkiksi teko "Osta kirja 50 markalla" vaatii kaksi alkeisosaa: esineen siirtyminen käyttäjälle ja rahan määrän väheneminen. Tietokannassa nämä olisivat toisin sanoen:
LAG:n tarjoamat tekojen alkeisosat ovat:
Teon osat on numerokoodattu ActionParts-tauluun. Niiden parametri(t) ja toteutus tietokantatasolla ovat vastaavasti:
Pseudokielinen koodi proseduurista löytyy liitteenä.
Proseduuri suorittaa teon käsittelyn alkuosan:
Proseduuri käynnistyy pelaajan valittua pelisivun jonkin teon. Pelisivu palauttaa WWW-palvelimen kautta argumentit UserID, Round ja ActionID.
Pseudokielinen koodi proseduurista löytyy liitteenä.
Proseduurin tärkeimmät tehtävät:
Pseudokielinen koodi proseduurista löytyy liitteenä.
Proseduuri luo paikkasivun käyttäjälle tietokannasta löytyvien tietojen perusteella. Näitä rutiineja käytetään yleisesti aina kun halutaan palauttaa pelaan nykyinen tilanne. Tällaisia tilanteita ovat pelissä teon suorituksen jälkeen, sisäänkirjautumisvaiheen tai päävalikon pelaamisvalinnan jälkeen, jos käyttäjällä on peli kesken. Proseduureja kutsutaan myöskin seikkailuvalikosta, seikkailun valinnan ja seikkailun "alustuksen" jälkeen.
Proseduurin tärkeimmät erityistehtävät:
PostScript-versio:
PROCEDURE ActionStart (userID [long int], round [long int], actionID [long int]) nextRound = AuthorizeUser (userID, round) IF nextRound = 0 THEN PROCEDURE END locID = DBGetUserLocation (userID) loc2ID = DBGetActionLocation (actionID) IF locID <> loc2ID THEN GO TO ERROR_HANDLER obstacleClass = DBGetActionObstacle (actionID) IF obstacleClass THEN obstacleOptions = DBGetObstacleOptions (actionID) beforeDescription = DBGetActionBeforeDescription (actionID) RUN RunObstacle.pl [userID, obstacleClass, obstacleOptions, actionID, beforeDescription] PROCEDURE END beforeDescription = DBGetActionBeforeDescription (actionID) IF beforeDescription THEN PrintHeader (userID, nextRound) PrintHiddenObject ("ActionID", actionID) PrintParagraph (beforeDescription) PrintLink ("Continue...", "/Game/ActionDo.pl") PrintFooter (userID) PrintFlushBuffer () ELSE RUN ActionDoDirect.pl [UserID, nextRound, ActionID] PROCEDURE END PROCEDURE ActionDo (userID [long int], round [long int], actionID [long int]) nextRound = AuthorizeUser (UserID, Round) IF nextRound = 0 THEN PROCEDUREN END RUN ActionDoDirect.pl [UserID, nextRound, ActionID, 0] PROCEDURE END PROCEDURE ActionDoDirect (userID [long int], nextRound [long int], actionID [long int], score [long int]) locID = DBGetUserLocation (userID) actionParts[]=DBGetActionParts (actionID) LOOP UNTIL kaikki actionParts[] käyty läpi SELECT CASE actionPart.type CASE 'Go' DBSetUserLocation (userID, actionPart.locationID) CASE 'Get' DBSetItemLocation (userID, actionPart.ItemID, CARRIED) CASE 'Drop' DBSetItemLocation (userID, actionPart.ItemID, locID) CASE 'Inc' propVal = DBGetPropertyValue (userID, actionPart.propertyID) DBSetPropertyValue (userID, actionPart.propertyID, propVal + actionPart.argument) CASE 'Dec' propVal = DBGetPropertyValue (userID, actionPart.propertyID) DBSetPropertyValue (userID, actionPart.propertyID, propVal - actionPart.argument) CASE 'MoveItem' DBSetItemLocation (userID, actionPart.ItemID, actionPart.locationID) done = DBActionDone (actionID, userID) IF NOT done THEN DBInsertActionsDone (actionID, userID, score) afterDescription = DBGetActionAfterDescription (actionID) IF afterDescription THEN PrintHeader (userID, nextRound) PrintParagraph (afterDescription) PrintLink ("Continue...", "/Game/CreateLocation.pl") PrintFooter (userID) PrintFlushBuffer () PROCEDUREN END RUN CreateLocationDirect.pl [userID, nextRound] PROCEDURE END PROCEDURE CreateLocation (userID [long int], round [long int]) nextRound = AuthorizeUser (UserID, Round) IF nextRound = 0 THEN PROCEDURE END RUN CreateLocation.pl [UserID, nextRound] PROCEDURE END PROCEDURE CreateLocationDirect (userID [long int], nextRound [long int]) PrintHeader (userID, nextRound) locID = DBGetUserLocation (userID) IF locID = NOWHERE_LOCATION_ID THEN RUN AdventureMenuDirect (userID, nextRound) PROCEDURE END beenIn = DBUserBeenInLocation (userID, locationID) IF NOT beenIn THEN DBInsertBeenIn (userID, locationID) locName = DBGetLocationName (locID) PrintTitle (locName) userProperties[] = DBGetUserProperties (userID) LOOP UNTIL kaikki userProperties[] käyty läpi name = DBGetPropertyName (userProperties.itemID) description = DBGetPropertyDescription (userProperties.itemID) amount = DBGetPropertyAmount (userProperties.itemID) pictureFile = DBGetPropertyPicture (userProperties.itemID) IF pictureFile THEN PrintPicture (PICTURE_PATH + pictureFile) PrintParagraph (name + " " + description + ": " + amount) userItems[] = DBGetLocationItems (userID, CARRIED) LOOP UNTIL kaikki userItems[] käyty läpi name = DBGetItemName (userItems.itemID) description = DBGetItemDescription (userItems.itemID) pictureFile = DBGetItemPicture (userItems.itemID) IF pictureFile THEN PrintPicture (PICTURE_PATH + pictureFile) PrintParagraph (name + " " + description) IF NOT beenIN THEN firstTimeText = DBGetLocationFirstTimeText (locID) PrintParagraph (firstTimeText) PrintFile (LOCATION_PATH + LOC_FILE_PREFIX + locName + LOC_FILE_SUFFIX) locItems[] = DBGetLocationItems (userID, locID) LOOP UNTIL kaikki locItems[] käyty läpi name = DBGetItemName (locItems.itemID) description = DBGetItemDescription (locItems.itemID) pictureFile = DBGetItemPicture (locItems.itemID) IF pictureFile THEN PrintPicture (PICTURE_PATH + pictureFile) PrintParagraph (name + " " + description) locActions[] = DBGetLocationActions (locID) LOOP UNTIL kaikki locActions[] käyty läpi actConds[] = DBGetActionConditions (actionID) LOOP UNTIL kaikki actConds[] käyty läpi SELECT CASE actCond.type CASE "Carried" itemLocID = DBGetItemLocation (userID, actCond.itemID) IF itemLocID <> CARRIED THEN NEXT ACTION CASE "Not Carried" itemLocID = DBGetItemLocation (userID, actCond.itemID) IF itemLocID = CARRIED THEN NEXT ACTION CASE "Greater than" propVal = DBGetPropertyValue (userID, actCond.propertyID) IF propVal > actCond.argument THEN NEXT ACTION CASE "Equal to" propVal = DBGetPropertyValue (userID, actCond.propertyID) IF propVal <> actCond.argument THEN NEXT ACTION CASE "Less than" propVal = DBGetPropertyValue (userID, actCond.propertyID) IF propVal < actCond.argument THEN NEXT ACTION CASE "Item in Location" itemLocID = DBGetItemLocation (userID, actCond.itemID) IF itemLocID <> actCond.locationID THEN NEXT ACTION CASE "Item not in Location" itemLocID = DBGetItemLocation (userID, actCond.itemID) IF itemLocID = actCond.locationID THEN NEXT ACTION PrintLink (locActions.description, "/LAG/ActionStart.pl " + locActions.actionID) PrintFooter (userID) PrintFlush () PROCEDURE END