Järjestelmäkäsikirja



Logiikka- ja pelaajanliittymämoduuli



Yleistä

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:

Proseduurit

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:

Yleisiä näkökohtia

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.

Seikkailun valinta ja alustaminen -Proseduuri AdventureMenu.pl

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).

Tekojen ehdot

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:

  1. Pelaajalla on esine (Item Carried). Vaatii, että esine on pelaajan mukana, eli CARRIED_LOCATION_ID-paikassa.

  2. Pelaajalla ei ole esinettä (Item Not Carried). Vaatii, että pelaajalla ei ole esinettä, eli esine ei ole CARRIED_LOCATION_ID-paikassa.

  3. Pelaajan ominaisuus on suurempi kuin (Property Greater Than). Vaatii, että pelaajan jokin ominaisuus on arvoltaan suurempi kuin tietty arvo. Arvo voi olla vain kiinteä, eikä esim. kahta ominaisuutta voida siten vertailla keskenään. Tämän kaltaiset tarpeet ovat kuitenkin harvinaisia (miettiessään olemassaolevien seikkailupelien toteuttamista LAG:n peruskomponenteilla, projektiryhmä ei havainnut tällaista tarvetta).

  4. Pelaajan ominaisuus on pienempi kuin (Property Less Than). Vaatii, että pelaajan jokin ominaisuus on arvoltaan pienempi kuin tietty arvo.

  5. Palaajan ominaisuus yhtäsuuri kuin (Property Equal To). Vaatii, että pelaajan jokin ominaisuus on täsmälleen sama kuin tietty arvo. Pelaajan ominaisuudet ovat kokonaislukutyyppisiä arvoja, mikä mahdollistaa tämänkaltaisen testin. Tämä testi voitaisiin korvata pienempi kuin - ja suurempi kuin -testeillä, mutta on helpompikäyttöinen. Ehtotyypit "suurempi tai yhtäsuuri kuin" ja "pienempi tai yhtäsuuri kuin" voidaan toteuttaa kokonaislukuaritmetiikassa helposti vähentämällä tai lisäämällä vertailuarvoa yhdellä.

  6. Esine on paikassa (Item In Location). Vaatii, että esine on tietyssä paikassa. Tällä ehdolla voitaisiin korvata Pelaajalla on esine -ehto, sillä sehän tutkii, onko esine paikassa CARRIED_LOCATION_ID. Pelaajalla on esine -toiminto on kuitenkin intuitiivisempi, ja on siksi otettu mukaan.

  7. Esine ei ole paikassa (Item Not In Location). Vaatii, ettei esine ole tietyssä paikassa. Pelaajalla ei ole esinettä -ehto voitaisiin korvata tällä.

Ehdot on numerokoodattu ActionConditions-tauluun. Ehtojen parametrit ja niitä vastaavat tietokannan kentät sekä toiminnan kuvaus ovat vastaavasti seuraavat:

  1. Esineen ID (ItemID). Tutkii ItemLocations-taulua. Vertaa esineen paikkaa CARRIED_LOCATION_ID:hen.

  2. Esineen ID (ItemID) . Tutkii ItemLocations-taulua. Vertaa esineen paikkaa CARRIED_LOCATION_ID:hen.

  3. Ominaisuuden ID (PropertyID), vertailuarvo (Argument). Tutkii PropertyValues-taulua.

  4. Ominaisuuden ID (PropertyID), vertailuarvo (Argument) . Tutkii PropertyValues-taulua.

  5. Ominaisuuden ID (PropertyID), vertailuarvo (Argument) . Tutkii PropertyValues-taulua.

  6. Esineen ID (ItemID), paikan ID (LocationID). Tutkii ItemLocations-taulua.

  7. Esineen ID (ItemID), paikan ID (LocationID). Tutkii ItemLocations-taulua.

Tekojen alkeisosat

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:

  1. Pelaajan siirtyminen (Go). Siirtää pelaajan tiettyyn paikkaan. Tämä on varmaankin yleisin teon osa. Jos se on teon ainoa osa, pelin kirjoittaja voisi antaa siitä pelaajalle esim. kuvauksen 'Go to ...', 'Move to ...'.

  2. Esineen ottaminen (Take). Siirtää esineen pelaajalle. Tämän ollessa ainoana osana teon kuvaukset voisivat olla esim. 'Get ...', 'Take ...'.

  3. Esineen siirtäminen nykyiseen paikkaan (Drop). Siirtää halutun esineen pelaajan nykyiseen paikkaan. Tämän teon osan käyttö merkitsee usein esineen 'pudottamista', mutta sillä voidaan periaatteessa siirtää mikä tahansa esine pelaajan nykyiseen paikkaan.

  4. Ominaisuus asettaminen arvoon (Set). Asettaa ominaisuuden haluttuun arvoon. Jos pelaaja esim. ryöstetään, voidaan tällä teon osalla asettaa rahan määräksi nolla.

  5. Ominaisuuden lisääminen määrällä (Inc). Lisää haluttua ominaisuutta halutulla määrällä.

  6. Ominaisuuden vähentäminen määrällä (Dec). Vähentää haluttua ominaisuutta halutulla määrällä.

  7. Esineen siirtäminen haluttuun paikkaan (MoveItem). Siirtää halutun esineen haluttuun paikkaan (mistä tahansa paikasta).

Teon osat on numerokoodattu ActionParts-tauluun. Niiden parametri(t) ja toteutus tietokantatasolla ovat vastaavasti:

  1. Paikan ID (LocationID). Asettaa Users-tauluun pelaajan aktiiviseksi paikaksi (CurrentLocationID) halutun paikan ID:n (LocationID:n).

  2. Esineen ID (ItemID). Asettaa ItemLocations-tauluun esineen (ItemID:n) tietueeseen esineen paikan (LocationID:n) arvoksi CARRIED_LOCATION_ID.

  3. Esineen ID (ItemID). Asettaa ItemLocations-tauluun esineen (ItemID:n) tietueeseen esineen paikan (LocationID:n) arvoksi käyttäjän nykyisen paikan (Users-taulun CurrentLocationID).

  4. Ominaisuuden ID (PropertyID), Arvo (Value). Asettaa PropertyValues-tauluun ominaisuuden (PropertyID) tietueeseen 'Value'-kenttään halutun arvon (Value)

  5. Ominaisuuden ID (PropertyID), Arvo (Value). Lisää PropertyValues-taulun ominaisuuden (PropertyID) tietueen 'Value'-kenttää halutulla arvolla (Value)

  6. Ominaisuuden ID (PropertyID), Arvo (Value). Vähentää PropertyValues-taulun ominaisuuden (PropertyID) tietueen 'Value'-kenttää halutulla arvolla (Value)

  7. Esineen ID (ItemID), paikan ID (LocationID). Asettaa ItemLocations-tauluun esineen (ItemID:n) tietueeseen esineen paikan arvoksi halutun paikan (LocationID).

Teon suorituksen alkuosa - Proseduuri ActionStart.pl

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.

Teon suorittaminen - Proseduurit ActionDo.pl, ActionDoDirect.pl

Pseudokielinen koodi proseduurista löytyy liitteenä.

Proseduurin tärkeimmät tehtävät:

Paikkasivun luominen - Proseduurit CreateLocation.pl, CreateLocationDirect.pl

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:

Liite: Modulaarinen jako

PostScript-versio:

Liite: Proseduurien kuvaukset Englanninkielen tyyppisellä proseduurikielellä


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