home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
RUN Flagazine: Run 6
/
run6.zip
/
GWBASIC6.ASC
< prev
next >
Wrap
Text File
|
1992-06-19
|
21KB
|
329 lines
De GWBASIC Interpreter (6)
RANDOMIZERS
Iedere programmeur wil zijn programma's levend en verrassend maken door het
gebruik van randomizers of willekeurige getallenkiezers. Randomizers zijn
nuttig en onmisbaar bij toepassingen als: games, kunstmatige intelligentie,
statistiek en vooral bij simulaties (nabootsingen van de werkelijkheid).
Om tot verrassende elementen of 'puur toeval' in onze programma's te komen,
kunnen we niet volstaan met een beroep op de GWBASIC-functie: RND. De op-
dracht in de directe modus:
PRINT RND
geeft weliswaar steeds een ander resultaat: 'n schijnbaar willekeurig decimaal
getal onder de 10 maar als we RND nader bekijken in een klein onderzoekspro-
grammaatje als hieronder, blijkt er van puur toeval totaal geen sprake meer te
┌────────────────────────────────────────────┐ zijn. Elke RUN levert 'n lijst-
│ 10 REM Genereer 10 willkeurige getallen │ je op met telkens dezelfde ge-
│ 30 FOR A%=1 TO 10 │ tallen: 1,6,8,7,7,0,4,4,1 en 9.
│ 40 PRINT "Willekeurig getal nr.";A%;":"; │ De functie RND moet eenmalig
│ 50 PRINT INT(RND*10) │ worden voorafgegaan door het
│ 60 NEXT A% │ commando: RANDOMIZE.
│ Ok │ Met dit commando wordt intern de
│ run │ randomizer aangezet maar dit ge-
│ Willekeurig getal nr. 1 : 1 │ beurt pas wanneer daarvoor een
│ Willekeurig getal nr. 2 : 6 │ uitgangs- of zaagetal aanwezig
│ Willekeurig getal nr. 3 : 8 │ is. Het programmaatje hiernaast
│ Willekeurig getal nr. 4 : 7 │ kan worden uitgebreid met de re-
│ Willekeurig getal nr. 5 : 7 │ gel:
│ Willekeurig getal nr. 6 : 0 │ 15 RANDOMIZE
│ Willekeurig getal nr. 7 : 4 │ Als we dat doen zal de interpe-
│ Willekeurig getal nr. 8 : 4 │ ter in deze regel 15 de afwer-
│ Willekeurig getal nr. 9 : 1 │ king van het programma onderbre-
│ Willekeurig getal nr. 10 : 9 │ ken om de gebruiker een zaadge-
└────────────────────────────────────────────┘ tal te vragen. Deze vraag luidt:
Random number seed (-32768 to 32767)
Van de gebruiker wordt verwacht dat hij een getal invoert in het gegeven bereik
maar het getuigt natuurlijk van slecht programmeerwerk wanneer een programma
een argeloze gebruiker met een dergelijke vraag overvalt.
Als programmeurs kunnen we de interne randomizer zelf een zaadgetal geven.
Niets hoeft ons ervan te weerhouden om de nieuwe regel 15 alsdus uit te
breiden:
15 RANDOMIZE 25 ┌─────────────────────────────┐
Verscheidene RUN's op het aangepaste programma │ run │
geven elke keer weer het resultaat zoals hier- │ Willekeurig getal nr. 1 : 1 │
naast is weergegeven. We hebben nu wel een an- │ Willekeurig getal nr. 2 : 3 │
getallenreeks gegenereerd maar elke keer ver- │ Willekeurig getal nr. 3 : 1 │
schijnen de getallen in dezelfde volgorde. │ Willekeurig getal nr. 4 : 8 │
De oorzaak laat zich niet moeilijk raden. De │ Willekeurig getal nr. 5 : 1 │
volgorde is bepaald door de waarde 25 die we │ Willekeurig getal nr. 6 : 9 │
als zaadgetal aan RANDOMIZE hebben meegegeven │ Willekeurig getal nr. 7 : 6 │
in regel 15. Om tot puur toeval te komen moe- │ Willekeurig getal nr. 8 : 4 │
ten we als zaadgetal een willekeurig getal op- │ Willekeurig getal nr. 9 : 5 │
geven maar hoe doen we dat? Hoe laten we ons │ Willekeurig getal nr. 10 : 8│
programma een willekeurig zaadgetal bedenken │ Ok │
in een programma dat zelf willekeurige getal- └─────────────────────────────┘
len moet maken?
Er zijn twee manieren voor. De eenvoudigste en meest gebruikte is die welke
gebruik maakt van de microtimer. Met GWBASIC-commando:
PRINT TIMER
krijgen we de inhoud te zien van de interne klok die microseconden telt. Het
kwartskristal levert daarvoor de invoer. Dit tellertje is gestart op het mo-
moment dat we de PC aanzetten. ┌─────────────────────────────────┐
TIMER is een interne systeemvariabele die │ 10 REM BESTUDEER MICROTIMER │
voortdurend anders zal zijn als hij wordt │ 20 CLS:KEY OFF │
opgevraagd met: │ 30 LOCATE 12,25 │
PRINT TIMER │ 40 PRINT "Aanvangstijd:";TIMER │
We krijgen de waarde tot op twee cijfers │ 50 WHILE INKEY$="" │
achter de decimale komma nauwkeurig. Deze │ 60 LOCATE 13,38 │
nauwkeurigheid kunnen we beperken tot hele │ 70 PRINT TIMER │
getallen of integers met de toevoeging: │ 80 WEND │
INT. Dus: │ 90 LOCATE 13,25 │
PRINT INT(TIMER) │ 100 PRINT "Stoptijd :";TIMER │
levert een geheel getal op en in een loop- └─────────────────────────────────┘
je of kringloop geeft dit een bruikbaar secondentellertje. TIMER of INT(TIMER)
kunnen we bij uitstek gebruik als zaadgetal voor RANDOMIZE omdat TIMER op elk
moment een andere waarde zal opleveren. Breiden we het programma van de vorige
pagina uit met:
15 RANDOMIZE TIMER
dan krijgen we onze zin en zal het resulterende lijstje telkens anders zijn. In
plaats van TIMER wordt ook de negatieve waarde: -TIMER gebruikt. Dat maakt
allemaal niets uit omdat het gevraagde zaadgetal zich over een bereik van
negatieve en positieve getallen uitstrekt.
True (?) randomizers
Het klinkt misschien gek maar een digitale computer als de PC is niet in staat
echte willekeurige getallen te genereren. Wanneer we gebruik maken van 'wille-
keurig' gekozen zaadgetallen, krijgen we wat we willen, namelijk telkens een
andere waarde van RND() maar heel principieel beschouwd is het resulterende
random getal niet echt random. Het lijkt erop. Daarom wordt er in het computer-
jargon gesproken van 'true randomizers'.
Studenten in de edele kunst van het programmeren komen niet onder de opgave uit
een spelletje te maken dat een willekeurig geheim getal onder de 100 kiest en
de speler laat raden wat het is. Na invoer krijgt de speler te zien of zijn
ingevoerde getal goed is geraden of dat het ingevoerde getal te hoog of te laag
is gegokt.
U gaat dit hooglaag-spelletje nu ook maken maar met wat we in de zes lessen tot
nu toe hebben geleerd, mogen bepaalde eisen aan het programma worden gesteld.
Het programma mag geen invoer accepteren die kleiner is dan nul of groter dan
99.
Wanneer het programma vaststelt dat de speler het geheime getal heeft geraden,
geeft het tot slot weer hoeveel beurten de speler er over heeft gedaan.
┌────────────────────┐
┌─────────────────────────────┤ Opgave 1 ├──────────────────────────┐
│100 REM HOOG LAAG SPEL └────────────────────┘ │
│110 CLS:KEY OFF:RANDOMIZE TIMER │
│120 COMPUTERKEUS=INT(RND*100) │
│130 INPUT "Welk getal (0 - 99)";SPELERKEUS │
│140 BEURT = BEURT + 1 │
│150 IF SPELERKEUS < 0 OR SPELERKEUS > 99 THEN BEEP:BEURT = BEURT - 1:GOTO 130│
│160 IF SPELERKEUS = COMPUTERKEUS THEN 190 │
│170 IF SPELERKEUS < COMPUTERKEUS THEN 200 │
│180 IF SPELERKEUS > COMPUTERKEUS THEN 210 │
│190 PRINT "Geraden in";BEURT;"beurten":END │
│200 PRINT "Te laag gegokt":GOTO 130 │
│210 PRINT "Te hoog gegokt":GOTO 130 │
└─────────────────────────────────────────────────────────────────────────────┘
De hierboven staande uitwerking van de vingeroefening geeft aanleiding tot een
opmerking. Regel 150 is nogal lang geworden. Dat komt doordat lange numerieke
variabelenamen zijn gebruikt maar ook doordat er na THEN drie dingen aan de
voorwaarden moeten voldoen. ALS de situatie zich voordoet dat de invoer kleiner
is dan 0 OF de situatie zich voordoet dat de invoer groter is dan 100 DAN
moeten er dingen gebeuren die nu volgen: een beepsignaal om de speler tot
opletten te manen, de beurt wordt ongeldig verklaard en de beurtteller wordt
met 1 verminderd en de programma-afwerking springt terug naar regel 130.
Lange regels zijn lelijk in GWBASIC. Zelf probeer ik ze in de RUN-listings voor
het AD zo veel mogelijk te vermijden. Maar bezwijk nooit voor de verleiding om
de opdrachten die aan de IF moeten voldoen in een nieuwe regel te schrijven.
Als u dat doet, voldoen ze niet meer aan de voorwaarden en uw programma gaat
zich misdragen. Het doet niet meer wat u er van verwacht.
Randomizer in DEF FN
Het moet u inmiddels zijn opgevallen dat GWBASIC een kist vol gereedschappen is
waarvan u er al heel wat kunt hanteren voor het maken van programmaatjes. Als u
met RND() aan de gang gaat, kunt u vooral in combinatie met andere gereed-
schappen, tot zeer verrassende resultaten komen. Het beroemde programma
ELIZA bijvoorbeeld is sterk randomizer-afhankelijk. Het lijkt alsof je met dit
programma over koetjes en kalfjes kunt praten maar in werkelijkheid kiest ELIZA
random uit een lijst van tekststrings. In sommige gevallen worden delen van wat
de gebruiker invoert, verweven in de willekeurig gekozen antwoorden. In de
praktijk gaat dan ongeveer zo:
╔════════════════════════════════════════════════════════════════════════════╗
║ Hallo, Ik ben Eliza. Wat is je probleem? ║
║ -- Mijn naam is Nico. ║
║ Namen interesseren me niet. ║
║ -- Ik heb ruzie met mijn broer. ║
║ Vertel daar eens meer over. ║
║ -- Mijn broer is onhebbelijk. ║
║ Misschien ben jij wel onhebbelijk. ║
║ -- Ik houd van reizen. ║
║ Daar wil ik meer over weten. ║
║ -- Ik was laatst in de Belgische plaats Namen. ║
║ Namen interesseren me niet. ║
╚════════════════════════════════════════════════════════════════════════════╝
U voelt 'm al: Dit nepprogramma kiest willekeurig uit nietszeggende zinnetjes
om de conversatie gaande te houden als het geen zinsdelen kan vinden die het
kan verweven in quasi-intelligente antwoorden. Vaak worden antwoorden met
verweven zinsdelen uit de invoer totaal verkeerd gebruikt.
Bij het programmeren van deze en andere programma's die uitbundig van rando-
mizers gebruik maken, moet vele, vele malen de opdracht:
X=INT(RND*x)+1
worden gebruikt. Dat kan ook eenmalig gebeuren met een weinig gebruikte maar
zeer nuttige zelf te maken functie. GWBASIC voorziet in het zelf maken van
functies en het later aanroepen van die functies met: DEF FN en FN. Evenals
RANDOMIZE wordt een DEF FN bovenin de programmalijst geplaatst. De interpreter
hoeft er slechts één keer voorbij te komen.
Een DEF FN kan van alles zijn maar in ons geval kunnen we van te voren bepalen
dat deze 'user DEFined FuNction' een randomizer gaat bevatten. Wanneer we de
volgende zelf te maken function eenmalig en aan het begin van het programma
declareren:
DEF FN X=INT(RND*99)+1
dan kunnen we hem in het werkende programma naar hartelust aanroepen met:
FN X.
De variabele X zal dan elke keer een willekeurig getal tussen 0 en 99 bevatten.
U kunt dit niet in de directe modus van GWBASIC uitproberen. Een DEF FN in de
directe modus geeft als foutmelding: Illegal direct. Daarom een piepklein
programmaatje om dit eens nader te bekijken:
┌───────────────────────────┐
│ 10 RANDOMIZE -TIMER │ Tik deze oefening in GWBASIC en geef RUN. U
│ 20 DEF FN X=INT(RND*99)+1 │ krijgt kolomsgewijs net zo veel willekeurig ge-
│ 30 WHILE INKEY$="" │ kozen getallen onder de 100 als u maar hebben
│ 40 PRINT FN X, │ wilt. Voor verdeling in kolommen zorgt de kom-
│ 50 WEND │ ma die in regel 30 na X als scheidingsteken fun-
└───────────────────────────┘ geert. Het genereren van de willekeurige getal-
len houdt pas op wanneer u op een toets drukt. Dit is het gevolg van de
WHILE/WEND die op een intoetsing wacht. Tussen de dubbele aanhalingstekens in
regel 20 staat niets; geen spatie dus! Als gevorderde kunt u toch lezen wat er
staat? ZOLANG (WHILE) er geen toets ("") wordt ingedrukt, druk het in DEF FN
gedeclareerde random getal (FN X) af (WEND).
Op de niet altijd even simpele maar toch wel erg machtige DEF FN komen we
verderop in de cursus nog uitgebreid terug.
Randomizers in ON x GOTO
Tussen neus en lippen door leren we nu even een handig techniekje dat door
velen vaak wordt gebruikt in een menugestuurd programma. Veel programma's
beginnen met een overzichtelijk menu van opties of keuzemogelijkheden.
Bijvoorbeeld:
┌───────────┐
╔═════════╡ HOOFDMENU ╞═════════╗
║ └───────────┘ ║
║ 1 - Zoeken op trefwoorden ║
║ 2 - Trefwoorden sorteren ║
║ 3 - Documenten invoeren ║
║ 4 - Systeemoverzicht ║
║ 5 - Statistiek ║
║ 6 - Einde programma ║
■═══════════════════════════════■
│ U kiest: 1, 2, 3, 4, 5 of 6 │
└───────────────────────────────┘
Als dit menu op het scherm staat verwacht de programmeur dat de gebruiker nu
de cijfertoetsen 1 tot en met 6 gaat indrukken. Alle andere intoetsingen worden
afgevangen. Het programma ontvangt de intoetsing en daarmee de keuze van de
gebruiker in een numerieke variabele uit, bijvoorbeeld:
400 INPUT K
410 IF K<1 AND K>7 THEN BEEP:GOTO 400
Wanneer in de variabele K een geldige menukeuze staat, gaat het programma
verder. Nu moet de programmeur zorgen dat het programma wegspringt naar het
deel in het programma waar de bestelde functie begint. Hij heeft zijn programma
netjes gestructureerd opgezet. Zoeken op trefwoorden begint op regel 1000,
Trefwoorden sorteren op regel 2000, Documenten invoeren op 3000,
Systeemoverzicht op 4000, Statistiek op 5000 en Einde programma op 6000.
De programmeur mag nu afzien van een constructie waarin komt te staan:
IF K=1 THEN GOTO 1000
IF K=2 THEN GOTO 2000
...enzovoort. In plaats daarvan beschikt hij over de functie:
ON [variabelenaam] GOTO [regelnummer].
De verkeersregeling gaat na regel 410 dan als volgt in zijn werk:
420 ON K GOTO 1000, 2000, 3000, 4000, 5000, 6000
In een programma dat de schijn wil ophouden dat het over iets beschikt dat doet
denken aan de menselijke vrije wil, kunnen we de ON x GOTO constructie
natuurlijk ook gebruiken met een randomizer. Stel dat in een simulatieprogramma
in 'willekeurige' volgorde continue bepaalde delen van het programma moeten
worden afgewerkt. Laten we aannemen dat die geroepen stukken netjes zijn onder-
gebracht in delen. Schematisch geprogrammeerd ziet dat er zo uit:
We hebben gebruik gemaakt van DEF FN ┌───────────────────────────────────────┐
functie in regel 20 om in regel 30 │ 10 CLS:KEY OFF:RANDOMIZE TIMER │
uit FN X steeds een andere waarde │ 20 DEF FN X=INT(RND*5)+1 │
voor de numerieke variabele X te ver- │ 30 ON FN X GOTO 60, 70, 80, 90, 100 │
krijgen. De ON in regel 30 doet braaf │ 40 PRINT X │
wat we willen. Omdat in X telkens een │ 50 REM Hieronder de 5 delen │
1, 2, 3, 4 of 5 komt te staan, wordt │ 60 PRINT "Dit is deel 1":GOTO 110 │
de programmabesturing telkens op een │ 70 PRINT "Dit is deel 2":GOTO 110 │
ander punt in het programma voortge- │ 80 PRINT "Dit is deel 3":GOTO 110 │
zet. We kunnen dat op twee manieren │ 90 PRINT "Dit is deel 4":GOTO 110 │
controleren. In regel 40 wordt X ter │ 100 PRINT "Dit is deel 5 │
controle op het scherm gezet en in de │ 110 PRINT "Toets voor vervolg" │
programmadelen wordt weergegeven om │ 120 WHILE INKEY$="":WEND:GOTO 30 │
welk programmadeel het op dat moment └───────────────────────────────────────┘
gaat. U ziet trouwens ook waarom in regel 100 geen doorverwijzing naar regel
110 nodig is; het is de regel die in de programmaflow volgt op regel 100.
Dit testprogramma gedraagt zich, evenals tal van andere als een perpetuum
mobile. De STOP of END ontbreekt. Stop dit programma met Shift+Break.
Muzikale grapjes met randomizers
Randomizers kunnen de fantasie van de programmeur enorm prikkelen. Sterker nog:
door te stoeien met functies en randomizers kun je tot ontdekkingen komen. Je
kunt tot dingen komen die nog nooit eerder zijn bedacht. Dat geldt bijvoorbeeld
voor grafische en geluidseffecten.
We hebben het muziek- en geluidsgedeelte van GWBASIC nog niet gehad maar niets
hoeft ons er van te weerhouden er onze randomizers op los te laten. Opgemerkt
is al dat randomizers de illusie van kunstmatige intelligentie kunnen opwekken.
Ze kunnen ook de illusie van kunsmatige creativiteit oproepen. Ik heb dat
geprobeerd met onderstaand programma waarin ik de PC muzikale akkoorden laat
genereren. Daarbij is uitgegaan van een simpel principe, namelijk dat muziek
voor een belangrijk deel op herhaling van voorgaande akkoorden maar dan met
verschillende toonhoogte is gebaseerd.
En warempel... dit principe hanterend worden er geluiden ten gehore gebracht
die meestal sterk aan gecomponeerde muziek doen denken.
╔════════════════════════════════════════════════════════════════════════════╗
║10 CLS:KEY OFF:RANDOMIZE TIMER ║
║20 M$="ABCDEFG...." ║
║30 DEF FN X=INT(RND*2)+12 ║
║40 DEF FN Y=INT(RND*11)+1 ║
║50 DEF FN H=INT(RND*2)+2 ║
║60 MUZIEK$="" ║
║70 FOR A%=1 TO FN X ║
║80 Z$=MID$(M$,FN Y,1) ║
║90 MUZIEK$=MUZIEK$+Z$ ║
║100 IF A%/6=INT(A%/6) THEN GOSUB 180:MUZIEK$=MUZIEK$+"MB"+Z$:PRINT MUZIEK$ ║
║110 IF A%/7=INT(A%/7) THEN GOSUB 190:MUZIEK$=MUZIEK$+"ML"+Z$:PRINT MUZIEK$ ║
║120 NEXT A% ║
║130 IF LEFT$(MUZIEK$,1)="." THEN MUZIEK$="":GOTO 30 ║
║140 GOSUB 180 ║
║150 PRINT:PRINT MUZIEK$ ║
║160 MUZIEK$=MUZIEK$+"MLL4EDEGAGFEDMNL2C...C." ║
║170 PLAY MUZIEK$:END ║
║180 Z$="O"+STR$(FN H)+MUZIEK$:RETURN ║
║190 Z$="L"+STR$((FN H)+2)+MUZIEK$:RETURN ║
╚════════════════════════════════════════════════════════════════════════════╝
Print dit programma uit (gebruik desnoods <Shift/Print Scrn>) en luister. U
zult het met me eens zijn dat er iets 'herkenbaars' in de akkoorden zit, maar
over de vraag of dit 'muzikaal' is of 'mooi' mag u van mening verschillen.
Merk op dat in de regels 30 tot en met 50 DEF FN is gebruikt voor de
randomizers die elders in het programma in actie komen.
Voor muziek wordt in GWBASIC het commando PLAY gebruikt. Dit is een nogal
veelzijdig commando dat eigenlijk verspild is aan het goedkope en vaak ook
kwalitatief slechts luidsprekertje waarmee PC's zijn uitgerust. In eerste
aanleg was het luidsprekertje alleen bedoeld om piepjes van SOUND en BEEP
hoorbaar te maken. PLAY verwacht een tekststring en het aardige is natuurlijk
dat zo'n tekststring willekeurig kan worden gemaakt door het gebruik van
randomizers.
Willkeurige getallen die door RND() worden gemaakt moeten voor de tekststring
naar tekst worden omgezet. Dit gebeurt in de regels 180 en 190 met de functie:
STR$(numerieke variabele). STR$ is dus het omgekeerde van VAL(tekst) waarmee
van een getal in de vorm van tekst een echt numeriek en verwerkbaar getal wordt
gemaakt.
Dat was het weer. Oefen vooral veel met het geleerde en bedenk zelf varianten.
Ik zie u over een paar maanden graag weer terug....
(Word vervolgd)
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀