home *** CD-ROM | disk | FTP | other *** search
/ RUN Flagazine Extra: Special 3 / run-special-3.zip / CURSUS.ASC < prev    next >
Text File  |  1992-08-05  |  41KB  |  639 lines

  1.  
  2.                          DE GWBASIC INTERPRETER (5)
  3.  
  4.                                 SUBROUTINES
  5.                            
  6. In deze les gaan we uitvoerig in op subroutines. Daar zijn we hard aan toe
  7. omdat we al een flink stuk van de lange weg achter ons hebben en het stadium
  8. van de gevorderde GWBASIC-programmeur bereiken. Dat wil zeggen: alléén voor
  9. diegenen die de voorgaande vier lessen hebben gevolgd en het geleerde met
  10. zelf bedachte 'toepassingen' in praktijk brengen. Programmeren leren is een
  11. beetje studeren maar heel veel zelf proberen en variëren op de thema's. 
  12. ╔═════════════════════════╗ Hiernaast ziet u de anatomie van een sub-
  13. ║       SUBROUTINE        ║ routine. 
  14. ║                         ║ Een subroutine is een programma IN een pro-
  15. ║     10 ..               ║ programma. Als ingebouwd programma kan de
  16. ║     20 ..               ║ subroutine overal en zo vaak als dat nodig
  17. ║┌─── 30 GOSUB 1000<──┐   ║ is, worden aangeroepen. Dat gebeurt met de
  18. ║│    40 ..           │   ║ opdracht: GOSUB xx waarin xx het regelnummer
  19. ║│    50 ..           │   ║ is waar de subroutine begint. De sprong naar
  20. ║│┌── 60 GOSUB 1000<─┐│   ║ dat regelnummer heeft hetzelfde effect als 
  21. ║││   70 ..          ││   ║ een GOTO-instructie met dit verschil dat de
  22. ║││ ┌ 80 GOSUB 1000<┐││   ║ programmagang binnen de subroutine vroeg of
  23. ║││ │ 90 END        │││   ║ laat opbotst tegen de instructie RETURN. 
  24. ║└┴─┴>1000 REM      │││   ║ Letterlijk betekent dit: Keer terug naar
  25. ║     1010 ..       │││   ║ waar je vandaan kwam! Dat gebeurt dan ook en
  26. ║     1020 ..       │││   ║ de afwerking van het hoofdprogramma wordt
  27. ║     1030 ..       │││   ║ na het subroutine-intermezzo hervat met de
  28. ║     1040 RETURN ──┴┴┘   ║ instructie die na GOSUB xx komt. Dat kan een
  29. ╚═════════════════════════╝ volgend regelnummer zijn of een volgende
  30. instructie die achter een dubbele punt staat zoals in: GOSUB 1000:CLS. In
  31. dit geval wordt gebruikt gemaakt van multiple statements.
  32.  
  33. Enorme voordelen
  34.  
  35. Het gebruik van subroutines betekent goed (en slim) programmeerwerk.
  36. Subroutines houden de listing kort, ze maken het programma sneller en ze
  37. vergemakkelijken het werk van de programmeur.
  38. Waarom? Wel, heel eenvoudig. Wie programmeert zal in zijn programma bepaalde
  39. onderdelen opnemen die tijdens het gebruik van het programma geregeld
  40. terugkomen en telkens hetzelfde zijn. In een interactief programma wordt
  41. keer op keer om een stukje invoer gevraagd of wordt de gebruiker gevraagd
  42. een bepaalde toets in te drukken om verder te gaan. Nu zul je als
  43. programmeur toch wel gek zijn als je dat elke keer wanneer dat in de
  44. voortgang van het programma aan de orde komt, weer opnieuw programmeert.
  45. Onderin het scherm met knipperende letters de tekst plaatsen: 'Toets <Esc>
  46. om verder te gaan', hoeft maar één keer in een subroutine-tje te worden
  47. opgenomen en telkens wanneer de programmeur dat nodig vindt, roept hij de
  48. subroutine aan.
  49.  
  50. Voorschriften
  51.  
  52. De GWBASIC-interpreter houdt een boekhouding bij van regelnummers. U weet
  53. ongetwijfeld dat u in de directe modus, achter de Ok-prompt een opdracht
  54. kunt invoeren. RENUM bijvoorbeeld, reorganiseert de regelnummers met stappen
  55. of increments van 10. U kunt ook een nieuwe regelnummering invoeren die b.v.
  56. begint bij 100 en stappen van 100 aanhoudt. De opdracht luidt dan: RENUM
  57. 100,100.
  58. RENUM is een intelligente functie van de interpreter. Hij zorgt er tijdens
  59. het hernummeren ook voor dat de consistentie van het programma intact
  60. blijft. Achter GOTO en GOSUB staat altijd een regelnummer. U begrijpt dat
  61. deze doorverwijzingen moeten meeveranderen als de integrale nummering wordt
  62. veranderd. Diezelfde boekhouding van regelnummers is ook oorzaak van twee
  63. foutmeldingen die bij het gebruik van subroutines kunnen optreden. Ze
  64. luiden:
  65. RETURN without GOSUB
  66. en
  67. GOSUB without RETURN.
  68. U voelt 'm al zitten. Elke subroutine MOET worden afgesloten met de opdracht
  69. RETURN. Vergeet u dit dan kunnen er twee dingen gebeuren: u krijgt de
  70. foutmelding GOSUB without RETURN of er wordt aan de subroutine begonnen en
  71. de interpreter zakt er letterlijk doorheen en gaat vrolijk verder met de
  72. instructies die direct na de lekke subroutine komen. Met andere woorden: een
  73. subroutine is alleen een subroutine als er een RETURN in staat.
  74. In GWBASIC worden subroutines altijd aan het einde van het programma gezet.
  75. Dit omdat subroutines aan de bovenkant open zijn. Zou een programma aan het
  76. begin met een subroutine beginnen dat begint de interpreter er vrolijk aan
  77. totdat hij de opdracht RETURN tegenkomt en zich afvraagt: 'Return? Hoezo?
  78. Waar naar toe? De foutmelding: Return without Gosub is het gevolg.
  79.  
  80. Variabelen doorgeven
  81.  
  82. Als GWBASIC-programmeur moet u bij het ontwerpen van uw programma goed
  83. boek houden over de subroutines die u gebruikt. Documenteer uw programma met
  84. REM's of apostrofjes waarin u voor u zelf aantekeningen maakt met betrekking
  85. tot de indeling van uw programma-flow en vooral over wat de subroutines
  86. doen.
  87. In de praktijk zitten onoplettende programmeurs heel vaak veranderingen aan
  88. te brengen in de verkeerde subroutines. Bestudeer en doorgrond het programma
  89. hieronder dat gebruik maakt van één subroutine. De subroutine tekent een
  90. enkellijns òf een dubbellijns kader in het midden van het scherm. Om onder-
  91. ┌───────────────────────────────────────────┐ scheid te kunnen maken tussen
  92. │ 100 CLS:KEY OFF                           │ enkel- en dubbellijns worden
  93. │ 110 LOCATE 12,30                          │ variabelen benoemd alvorens
  94. │ 120 PRINT "Druk op de <Enter> toets"      │ de sprong naar de subroutine
  95. │ 130 HLB=218:HRB=191:HLO=192:HRO=217       │ vanaf regelnummer 500 wordt 
  96. │ 140 LH=196:LV=179                         │ gemaakt. Deze variabelen heb-
  97. │ 150 GOSUB 500 'Teken enkellijns kader     │ ben herkenbare namen:
  98. │ 160 CLS:LOCATE 12,30                      │ HLB = Hoek Links Boven
  99. │ 170 PRINT "Druk op de <Enter> toets"      │ HRB = Hoek Rechts Boven
  100. │ 180 HLB=201:HRB=187:HLO=200:HRO=188       │ HLO = Hoek Links Onder
  101. │ 190 LH=205:LV=186                         │ HRO = Hoek Rechts Onder
  102. │ 200 GOSUB 500 'Teken dubbellijns kader    │ LH  = Lijn Horizontaal
  103. │ 210 END                                   │ LV  = Lijn Verticaal
  104. │ 500 REM Enkel- of dubbellijns kader       │ De waarden die aan deze vari-
  105. │ 510 LOCATE 10,28                          │ abelen worden toegekend, cor-
  106. │ 520 PRINT CHR$(HLB)STRING$(26,LH)CHR$(HRB)│ responderen met de ASCII-
  107. │ 530 FOR A%=11 TO 13                       │ waarden die u inmiddels hebt
  108. │ 540   LOCATE A%,28                        │ leren kennen en programmeren.
  109. │ 550   PRINT CHR$(LV)                      │ Loop ze na en het zal u dui-
  110. │ 560   LOCATE A%,55                        │ lijk worden hoe vóór de eerste
  111. │ 570   PRINT CHR$(LV)                      │ sprong de variabelen de vol-
  112. │ 580 NEXT A%                               │ gende betekenissen krijgen:
  113. │ 590 LOCATE 14,28                          │ HLB = ┌
  114. │ 600 PRINT CHR$(HLO)STRING$(26,LH)CHR$(HRO)│ HRB = ┐
  115. │ 610 WHILE INKEY$<>CHR$(13):WEND           │ HLO = └
  116. │ 620 RETURN                                │ HRO = ┘
  117. └───────────────────────────────────────────┘ LH  = ─
  118.                                               LV  = │
  119. Uit deze ASCII-tekens kan een perfect enkellijns kader om de tekst: 'Druk op
  120. de <Enter> toets' worden getekend. Dat vanaf regel 160 hetzelfde gebeurt
  121. maar dan met ASCII-tekens die een dubbellijns kader tekenen, had u al
  122. begrepen.
  123.  
  124. Praktisch gebruik
  125.  
  126. In de praktijk worden subroutines veelvuldig gebruikt. In de RUN-listings
  127. van de GWBASIC-rubriek in het Algemeen Dagblad eindigen de laatste regels
  128. bijna altijd op: RETURN. Daar zijn twee dwingende redenen voor. Subroutines
  129. worden gebruikt om de listing zo kort mogelijk te houden (plaatsruimte is
  130. duur in een landelijk dagblad, vooral op zaterdag). Ten tweede komen
  131. subroutines de veelzijdigheid van een programma ten goede, vooral wanneer er
  132. ook nog met variabele waarden wordt gewerkt.
  133. Subroutines zijn ideaal voor het afhandelen van de 'huishoudelijke' taken
  134. binnen een programma. Ik bedoel daarmee dat er gemakkelijk vaak weerkerende
  135. dingen in worden ondergebracht als het invoeren van gegevens door de
  136. gebruiker op het indrukken van een bepaalde toets om het programma verder te
  137. laten gaan.
  138.  
  139. Toetsen afvangen
  140.  
  141. 'Druk op een toets om verder te gaan'
  142. 'Toets <Enter>'
  143. 'Druk op <Esc> voor RUN Desktop'
  144. '<Enter> is verder gaan - <Esc> is einde'
  145. 'Spiralen --------> 1
  146.  Driehoeken ------> 2
  147.  Bollen ----------> 3'
  148.  
  149. Dit zijn zo de 'kreten' die de programmeur gebruikt om zijn gebruiker iets
  150. te laten doen. Direct nadat hij zo'n 'kreet' op het scherm heeft geplaatst,
  151. stuurt hij zijn programma naar een subroutine die gaat staan wachten tot de
  152. gebruiker doet wat hem wordt gevraagd. De subroutine reageert op de
  153. intoetsing door iets te doen (het scherm wissen bijvoorbeeld of een
  154. berekening uitvoeren) en geeft vervolgens de besturing over het programma
  155. terug. De programmeur wéét dat hij de besturing direct na RETURN weer
  156. terugkrijgt. In feite heeft hij naar het gebeuren in de subroutine geen
  157. omkijken, zo lang het 'daar onderin' maar goed geprogrammeerd is.
  158. Omdat in subroutines vaak intoetsingen van de gebruiker worden verwerkt, is
  159. het zaak daar eens even bij stil te staan. Veel programmeurs maken zich hier
  160. met een Jantje van Leiden af en hebben een zeer grote voorkeur voor:
  161. 'Druk op een toets'.
  162. Waarom? Omdat een subroutinetje om dit af te handelen heel weinig
  163. programmeerwerk betekent:
  164.  
  165. 500 WHILE INKEY$="":WEND:RETURN
  166. of
  167. 500 IF INKEY$="" THEN GOTO 500 ELSE RETURN
  168.  
  169. Letterlijk staat er: ZO LANG er niet op een toets wordt gedrukt, doe dan
  170. niets. Of: INDIEN er niet op een toets worden gedrukt, GA NAAR hetzelfde
  171. regelnummer. Een beetje programmeur houdt hier niet van. Het is namelijk niet
  172. waar wat er staat. De gebruiker kan niet op 'any' toets drukken om het
  173. programma te laten reageren. Op <Shift>, <Ctrl> en <Alt> wòrdt niet
  174. gereageerd. Eleganter is aan bepaalde intoetsingen een vaste betekenis toe te
  175. kennen. In RUN Flagazine wordt consequent de <Esc> gebruikt om terug te keren
  176. naar de RUN Desktop.
  177. Als u voor speciale intoetsingen kiest, moet u ze in de betreffende
  178. subroutine ook netjes 'afvangen'. U kunt niet op het scherm zetten: 'Toets
  179. <Enter>' en u er in de subroutine van af maken met een regeltje als in de
  180. twee hierboven gegeven voorbeelden. Als de gebruiker ontdekt dat hij behalve
  181. op <Enter> ook op de spatiebalk of een lettertoets kan drukken om hetzelfde
  182. gedaan te krijgen, maakt dat op z'n minste een wat slordige indruk.
  183. De <Enter> vangen we derhalve netjes af:
  184.                                      ┌─────────────────────────────────────┐
  185. Er staat letterlijk: ZOLANG er op    │ 500 REM SUBROUTINE VANGT <Enter>    │
  186. een ANDERE toets wordt gedrukt dan   │ 510 WHILE INKEY$ <> CHR$(13):WEND   │
  187. de <Enter>, DOE dan niets en blijf   │ 510 RETURN                          │
  188. naar het toetsenbord kijken. Wordt   └─────────────────────────────────────┘
  189. er wèl op <Enter> gedrukt, keer dan terug. Dit werkt lekker zo lang het om
  190. slechts één bepaalde toets gaat. Heeft de gebruiker de keuze tussen
  191. bijvoorbeeld de <Enter> en de <Esc> dan wordt het iets ingewikkelder. We
  192. kunnen dan niet meer volstaan met
  193.                                    ┌────────────────────────────────────────┐
  194. het handige INKEY$ maar we zullen  │ 500 REM SUBROUTINE VANGT <Enter>/<Esc> │
  195. een variabele moeten introduceren  │ 510 I$=INKEY$                          │
  196. om het onderscheid te kunnen aan-  │ 520 WHILE I$<>CHR$(13) AND I$<>CHR$(27)│
  197. geven. Gemakshalve wordt hiervoor  │ 530   I$=INKEY$                        │
  198. steevast de stringvariabele I$ ge- │ 540 WEND                               │
  199. gebruikt. In 510 wordt I$ aange-   │ 550 IF I$=CHR$(13) THEN CLS:END        │
  200. wezen om naar het toetsenbord te   │ 560 RETURN                             │
  201. kijken. De ASCII-waarde van de in- └────────────────────────────────────────┘
  202. toetsing komt in I$ te staan. In 520 kijken we wàt er wordt ingetoetst.
  203. Zolang de ASCII-waarde van I$ niet die van <Enter> (CHR$(13)) is of van
  204. <Esc>
  205. (CHR$(27)), wordt I$ in 530 geleegd en met WEND op het toetsenbord gericht.
  206. Eens moet de tijd komen dat de gebruiker in de gaten krijgt dat hij de keus
  207. heeft uit twee toetsen. Toets hij òf <Enter> òf <Esc> in dan passeert het
  208. programma de WEND in 540. Nu kunnen we kijken welke van de twee is
  209. ingedrukt.
  210. Is het <Enter> dan: scherm schoon en einde programma. In elk ander geval
  211. (maar in ons geval kan het alleen nog maar de <Esc> zijn) wordt regel 550
  212. gepasseerd en komt de subroutine zijn onvermijdelijke RETURN tegen.
  213. Nog bewerkelijker wordt het afvangen van intoetsingen als het om een
  214. keuzemenuutje gaat. Stel dat u uw gebruiker een menuutje voorschotelt voor 
  215. ╔════════════════════════════╗ een hypotheekberekening zoals hiernaast is
  216. ║ U kunt kiezen uit:         ║ afgebeeld. Wat verwachten we van de gebrui-
  217. ║                            ║ ker? Simpel: dat hij 1, 2, 3, 4, 5, 6 òf 7
  218. ║ 1. Berekening over  1 jaar ║ indrukt en niets anders. We zullen deze ze-
  219. ║ 2. Berekening over  2 jaar ║ ven intoetsingen moeten afvangen. Gemakke-
  220. ║ 3. Berekening over  5 jaar ║ lijk zou zijn als we dat niet met een INKEY$
  221. ║ 4. Berekening over 10 jaar ║ doen maar met een INPUT of een LINEINPUT, ge-
  222. ║ 5. Berekening over 20 jaar ║ volgd door een pets op <Enter>. Dat doen we
  223. ║ 6. Berekening over 30 jaar ║ niet omdat we het de gebruiker zo gemakke-
  224. ║ 7. Einde programma         ║ lijk mogelijk willen maken. Van ons hoeft hij
  225. ║                            ║ niet op twee toetsen te drukken maar volstaat
  226. ║ Uw keus: 1,2,3,4,5,6 of 7  ║ alléén de druk op de gewenste cijfertoets.
  227. ╚════════════════════════════╝ Aan een INKEY$-constructies valt dan niet te
  228. ontkomen. Ondergebracht in een subroutine zou dat er als volgt kunnen
  229. uitzien:
  230. ┌─────────────────────────────────────────────────────────────────────────┐
  231. │ 500 REM SUBROUTINE VANGT 1 T/M 7                                        │
  232. │ 510 I$=INKEY$                                                           │
  233. │ 520 IF I$<>"1" AND I$<>"2" AND I$<>"3" AND I$<>"4" AND I$<>"5" AND I$ <>│
  234. │ "6" AND I$<>"7" THEN BEEP:GOTO 510                                      │
  235. │ 530 IF I$="1" THEN JAAR%=1:GOTO 600                                     │
  236. │ 540 IF I$="2" THEN JAAR%=2:GOTO 600                                     │
  237. │ 550 IF I$="3" THEN JAAR%=5:GOTO 600                                     │
  238. │ 560 IF I$="4" THEN JAAR%=10:GOTO 600                                    │
  239. │ 570 IF I$="5" THEN JAAR%=20:GOTO 600                                    │
  240. │ 580 IF I$="6" THEN JAAR%=30:GOTO 600                                    │
  241. │ 590 IF I$="7" THEN CLS:END                                              │
  242. │ 600 RETURN                                                              │
  243. └─────────────────────────────────────────────────────────────────────────┘
  244. Dat is heel wat programmeerwerk om het de gebruiker een ietsje gemakkelijker
  245. te maken. Maar ja, klanten en computergebruikers zijn nu eenmaal koning en
  246. daar doe je alles voor.
  247. Nu we toch bezig zijn puntjes op de i te zetten; maak in uw subroutines voor
  248. het afvangen van intoetsing óók altijd onderscheid tussen hoofd- en kleine
  249. letters wanneer het om letterintoetsingen gaat. Wanneer we willen dat de
  250. gebruiker een lettertoets indrukt, ga  ╔═════════════════════╗
  251. er dan niet automatisch van uit dat    ║ Toets <I>nkoop      ║
  252. er op een kleine letter zal worden     ║       <V>erkoop     ║
  253. gedrukt. Het kan best zijn dat die     ║       <O>verzicht   ║
  254. gebruiker in zijn argeloosheid op      ║       <S>tatistiek  ║
  255. de toets <Caps Lock> heeft gedrukt.    ╚═════════════════════╝
  256. Ook deze eventualiteit moet worden afgevangen en wel zodanig dat het niet
  257. uitmaakt of de gebruiker nu op hoofdletters of op kleine letters drukt. En
  258. weer komt het werk daarvoor op de nek van de programmeur terecht.
  259. ┌───────────────────────────────────────────────────────────────────────┐
  260. │ 500 REM SUBROUTINE VANGT LETTERS I,V,O EN S                           │
  261. │ 510 I$=INKEY$                                                         │
  262. │ 520 WHILE I$<>"I" AND I$<>"i" AND I$<>"V" AND I$<>"v" AND I$<>"O" AND │
  263. │ I$<>"o" AND I$<>"S" AND I$<>"s"                                       │
  264. │ 530   I$=INKEY$                                                       │
  265. │ 540 WEND                                                              │
  266. │ 550 IF I$="I" OR I$="i" THEN ... : GOTO 580                           │
  267. │ 560 IF I$="V" OR I$="v" THEN ... : GOTO 580                           │
  268. │ 570 IF I$="O" OR I$="o" THEN ... : GOTO 580                           │
  269. │ 570 IF i$="S" OR I$="s" THEN ...                                      │
  270. │ 580 RETURN                                                            │
  271. └───────────────────────────────────────────────────────────────────────┘
  272.  
  273. Operatoren AND en OR
  274.  
  275. Stiekem hebben we in deze exercitie twee zogenoemde Booleaanse of logische
  276. operatoren geïntroduceerd: AND en OR. De functie ligt voor de hand. Bij het
  277. gebruik van AND in een IF/THEN-constructie moet aan beide voorwaarden worden
  278. voldaan. Bij het gebruik van OR hoeft aan slechts één van de opgenomen
  279. voorwaarden te worden voldaan.
  280. Kijken we nu even naar het afvangen van de letters I,V,O en S dan zien we in
  281. de WHILE/WEND-constructie in regel 520 dat alle voorwaarden in negatieve zin
  282. moet worden voldaan. In gewone taal: ZOLANG de intoetsing in I$ géén I, i,
  283. V, v, O, o, S EN s oplevert, maak I$ dan weer leeg en zet hem weer op het
  284. toetsenbord.
  285. Vanaf regel 550 zeggen we dat INDIEN de intoetsing een gevraagde hoofdletter
  286. OF een gevraagde kleine letter oplevert DAN is aan een voorwaarde voldaan en
  287. kunnen we het programma de maatregelen laten nemen die in overeenstemming
  288. zijn met de wens van de gebruiker.
  289.  
  290. Tot slot
  291.  
  292. We hebben nu vijf lessen achter de rug. Heeft u ze allemaal gevolgd dan kunt
  293. u al programmeren. U heeft de gereedschappen in huis om kleine
  294. programmaatjes te maken zoals het maken van tafels of het geven van kleine
  295. rekenopdrachtjes aan kinderen en vervolgens kijken of ze een sommetje goed of
  296. fout hebben. Ook hebt u tekstBEwerking gehad.
  297. Het wordt tijd om van uw kant ook eens iets te laten horen. We weten dan of
  298. deze cursus nog zinvol is. Ik geef u daarom een opdrachtje:
  299.                            ┌─────────────────────┐                          
  300. ┌──────────────────────────┤ PROGRAMMEEROPDRACHT ├─────────────────────────┐
  301. │                          └─────────────────────┘                         │
  302. │ Maak een programmaatje waarin u een kind enkele zinnen op het scherm     │
  303. │ laat lezen. In elk zinnetje zet u een taalfout. Het kind moet de taal-   │
  304. │ fout ontdekken en het zinnetje zonder de fout overtypen.                 │
  305. │ Controleer of de invoer correct is en laat uw programma daarop reageren. │
  306. │ Beloon het kind als hij het goed doet en 'tik hem op de vingers' als hij │
  307. │ het niet goed doet. Geef desnoods een rapportcijfer.                     │
  308. └──────────────────────────────────────────────────────────────────────────┘
  309.  
  310.  
  311.                            De GWBASIC Interpreter (6)
  312.  
  313.                                    RANDOMIZERS
  314.  
  315. Iedere programmeur wil zijn programma's levend en verrassend maken door het
  316. gebruik van randomizers of willekeurige getallenkiezers. Randomizers zijn
  317. nuttig en onmisbaar bij toepassingen als: games, kunstmatige intelligentie,
  318. statistiek en vooral bij simulaties (nabootsingen van de werkelijkheid).
  319. Om tot verrassende elementen of 'puur toeval' in onze programma's te komen,
  320. kunnen we niet volstaan met een beroep op de GWBASIC-functie: RND. De op-
  321. dracht in de directe modus:
  322. PRINT RND
  323. geeft weliswaar steeds een ander resultaat: 'n schijnbaar willekeurig decimaal
  324. getal onder de 10 maar als we RND nader bekijken in een klein onderzoekspro-
  325. grammaatje als hieronder, blijkt er van puur toeval totaal geen sprake meer te
  326. ┌────────────────────────────────────────────┐ zijn. Elke RUN levert 'n lijst-
  327. │ 10 REM Genereer 10 willkeurige getallen    │ je op met telkens dezelfde ge-
  328. │ 30 FOR A%=1 TO 10                          │ tallen: 1,6,8,7,7,0,4,4,1 en 9.
  329. │ 40   PRINT "Willekeurig getal nr.";A%;":"; │ De functie RND moet eenmalig
  330. │ 50   PRINT INT(RND*10)                     │ worden voorafgegaan door het
  331. │ 60 NEXT A%                                 │ commando: RANDOMIZE.
  332. │ Ok                                         │ Met dit commando wordt intern de
  333. │ run                                        │ randomizer aangezet maar dit ge-
  334. │ Willekeurig getal nr. 1 : 1                │ beurt pas wanneer daarvoor een
  335. │ Willekeurig getal nr. 2 : 6                │ uitgangs- of zaagetal aanwezig
  336. │ Willekeurig getal nr. 3 : 8                │ is. Het programmaatje hiernaast
  337. │ Willekeurig getal nr. 4 : 7                │ kan worden uitgebreid met de re-
  338. │ Willekeurig getal nr. 5 : 7                │ gel:
  339. │ Willekeurig getal nr. 6 : 0                │ 15 RANDOMIZE
  340. │ Willekeurig getal nr. 7 : 4                │ Als we dat doen zal de interpe-
  341. │ Willekeurig getal nr. 8 : 4                │ ter in deze regel 15 de afwer-
  342. │ Willekeurig getal nr. 9 : 1                │ king van het programma onderbre-
  343. │ Willekeurig getal nr. 10 : 9               │ ken om de gebruiker een zaadge-
  344. └────────────────────────────────────────────┘ tal te vragen. Deze vraag luidt:
  345. Random number seed (-32768 to 32767)
  346. Van de gebruiker wordt verwacht dat hij een getal invoert in het gegeven bereik
  347. maar het getuigt natuurlijk van slecht programmeerwerk wanneer een programma
  348. een argeloze gebruiker met een dergelijke vraag overvalt.
  349. Als programmeurs kunnen we de interne randomizer zelf een zaadgetal geven.
  350. Niets hoeft ons ervan te weerhouden om de nieuwe regel 15 alsdus uit te
  351. breiden:
  352. 15 RANDOMIZE 25                                ┌─────────────────────────────┐
  353. Verscheidene RUN's op het aangepaste programma │ run                         │
  354. geven elke keer weer het resultaat zoals hier- │ Willekeurig getal nr. 1 : 1 │
  355. naast is weergegeven. We hebben nu wel een an- │ Willekeurig getal nr. 2 : 3 │
  356. getallenreeks gegenereerd maar elke keer ver-  │ Willekeurig getal nr. 3 : 1 │
  357. schijnen de getallen in dezelfde volgorde.     │ Willekeurig getal nr. 4 : 8 │
  358. De oorzaak laat zich niet moeilijk raden. De   │ Willekeurig getal nr. 5 : 1 │
  359. volgorde is bepaald door de waarde 25 die we   │ Willekeurig getal nr. 6 : 9 │
  360. als zaadgetal aan RANDOMIZE hebben meegegeven  │ Willekeurig getal nr. 7 : 6 │
  361. in regel 15. Om tot puur toeval te komen moe-  │ Willekeurig getal nr. 8 : 4 │
  362. ten we als zaadgetal een willekeurig getal op- │ Willekeurig getal nr. 9 : 5 │
  363. geven maar hoe doen we dat? Hoe laten we ons   │ Willekeurig getal nr. 10 : 8│
  364. programma een willekeurig zaadgetal bedenken   │ Ok                          │
  365. in een programma dat zelf willekeurige getal-  └─────────────────────────────┘
  366. len moet maken?
  367. Er zijn twee manieren voor. De eenvoudigste en meest gebruikte is die welke
  368. gebruik maakt van de microtimer. Met GWBASIC-commando:
  369. PRINT TIMER
  370. krijgen we de inhoud te zien van de interne klok die microseconden telt. Het
  371. kwartskristal levert daarvoor de invoer. Dit tellertje is gestart op het mo-
  372. moment dat we de PC aanzetten.             ┌─────────────────────────────────┐
  373. TIMER is een interne systeemvariabele die  │ 10 REM BESTUDEER MICROTIMER     │
  374. voortdurend anders zal zijn als hij wordt  │ 20 CLS:KEY OFF                  │
  375. opgevraagd met:                            │ 30 LOCATE 12,25                 │
  376. PRINT TIMER                                │ 40 PRINT "Aanvangstijd:";TIMER  │
  377. We krijgen de waarde tot op twee cijfers   │ 50 WHILE INKEY$=""              │
  378. achter de decimale komma nauwkeurig. Deze  │ 60   LOCATE 13,38               │
  379. nauwkeurigheid kunnen we beperken tot hele │ 70   PRINT TIMER                │
  380. getallen of integers met de toevoeging:    │ 80 WEND                         │
  381. INT. Dus:                                  │ 90 LOCATE 13,25                 │
  382. PRINT INT(TIMER)                           │ 100 PRINT "Stoptijd    :";TIMER │
  383. levert een geheel getal op en in een loop- └─────────────────────────────────┘
  384. je of kringloop geeft dit een bruikbaar secondentellertje. TIMER of INT(TIMER)
  385. kunnen we bij uitstek gebruik als zaadgetal voor RANDOMIZE omdat TIMER op elk
  386. moment een andere waarde zal opleveren. Breiden we het programma van de vorige
  387. pagina uit met:
  388. 15 RANDOMIZE TIMER
  389. dan krijgen we onze zin en zal het resulterende lijstje telkens anders zijn. In
  390. plaats van TIMER wordt ook de negatieve waarde: -TIMER gebruikt. Dat maakt
  391. allemaal niets uit omdat het gevraagde zaadgetal zich over een bereik van
  392. negatieve en positieve getallen uitstrekt.
  393.  
  394. True (?) randomizers
  395.  
  396. Het klinkt misschien gek maar een digitale computer als de PC is niet in staat
  397. echte willekeurige getallen te genereren. Wanneer we gebruik maken van 'wille-
  398. keurig' gekozen zaadgetallen, krijgen we wat we willen, namelijk telkens een
  399. andere waarde van RND() maar heel principieel beschouwd is het resulterende
  400. random getal niet echt random. Het lijkt erop. Daarom wordt er in het computer-
  401. jargon gesproken van 'true randomizers'.
  402. Studenten in de edele kunst van het programmeren komen niet onder de opgave uit
  403. een spelletje te maken dat een willekeurig geheim getal onder de 100 kiest en
  404. de speler laat raden wat het is. Na invoer krijgt de speler te zien of zijn
  405. ingevoerde getal goed is geraden of dat het ingevoerde getal te hoog of te laag
  406. is gegokt.
  407. U gaat dit hooglaag-spelletje nu ook maken maar met wat we in de zes lessen tot
  408. nu toe hebben geleerd, mogen bepaalde eisen aan het programma worden gesteld.
  409. Het programma mag geen invoer accepteren die kleiner is dan nul of groter dan
  410. 99.
  411. Wanneer het programma vaststelt dat de speler het geheime getal heeft geraden,
  412. geeft het tot slot weer hoeveel beurten de speler er over heeft gedaan.
  413.                               ┌────────────────────┐
  414. ┌─────────────────────────────┤      Opgave 1      ├──────────────────────────┐
  415. │100 REM HOOG LAAG SPEL       └────────────────────┘                          │
  416. │110 CLS:KEY OFF:RANDOMIZE TIMER                                              │
  417. │120 COMPUTERKEUS=INT(RND*100)                                                │
  418. │130 INPUT "Welk getal (0 - 99)";SPELERKEUS                                   │
  419. │140 BEURT = BEURT + 1                                                        │
  420. │150 IF SPELERKEUS < 0 OR SPELERKEUS > 99 THEN BEEP:BEURT = BEURT - 1:GOTO 130│
  421. │160 IF SPELERKEUS = COMPUTERKEUS THEN 190                                    │
  422. │170 IF SPELERKEUS < COMPUTERKEUS THEN 200                                    │
  423. │180 IF SPELERKEUS > COMPUTERKEUS THEN 210                                    │
  424. │190 PRINT "Geraden in";BEURT;"beurten":END                                   │
  425. │200 PRINT "Te laag gegokt":GOTO 130                                          │
  426. │210 PRINT "Te hoog gegokt":GOTO 130                                          │
  427. └─────────────────────────────────────────────────────────────────────────────┘
  428.  
  429. De hierboven staande uitwerking van de vingeroefening geeft aanleiding tot een
  430. opmerking. Regel 150 is nogal lang geworden. Dat komt doordat lange numerieke
  431. variabelenamen zijn gebruikt maar ook doordat er na THEN drie dingen aan de
  432. voorwaarden moeten voldoen. ALS de situatie zich voordoet dat de invoer kleiner
  433. is dan 0 OF de situatie zich voordoet dat de invoer groter is dan 100 DAN
  434. moeten er dingen gebeuren die nu volgen: een beepsignaal om de speler tot
  435. opletten te manen, de beurt wordt ongeldig verklaard en de beurtteller wordt
  436. met 1 verminderd en de programma-afwerking springt terug naar regel 130.
  437. Lange regels zijn lelijk in GWBASIC. Zelf probeer ik ze in de RUN-listings voor
  438. het AD zo veel mogelijk te vermijden. Maar bezwijk nooit voor de verleiding om
  439. de opdrachten die aan de IF moeten voldoen in een nieuwe regel te schrijven.
  440. Als u dat doet, voldoen ze niet meer aan de voorwaarden en uw programma gaat
  441. zich misdragen. Het doet niet meer wat u er van verwacht.
  442.  
  443. Randomizer in DEF FN
  444.  
  445. Het moet u inmiddels zijn opgevallen dat GWBASIC een kist vol gereedschappen is
  446. waarvan u er al heel wat kunt hanteren voor het maken van programmaatjes. Als u
  447. met RND() aan de gang gaat, kunt u vooral in combinatie met andere gereed-
  448. schappen, tot zeer verrassende resultaten komen. Het beroemde programma
  449. ELIZA bijvoorbeeld is sterk randomizer-afhankelijk. Het lijkt alsof je met dit
  450. programma over koetjes en kalfjes kunt praten maar in werkelijkheid kiest ELIZA
  451. random uit een lijst van tekststrings. In sommige gevallen worden delen van wat
  452. de gebruiker invoert, verweven in de willekeurig gekozen antwoorden. In de
  453. praktijk gaat dan ongeveer zo:
  454. ╔════════════════════════════════════════════════════════════════════════════╗
  455. ║ Hallo, Ik ben Eliza. Wat is je probleem?                                   ║
  456. ║ -- Mijn naam is Nico.                                                      ║
  457. ║ Namen interesseren me niet.                                                ║
  458. ║ -- Ik heb ruzie met mijn broer.                                            ║
  459. ║ Vertel daar eens meer over.                                                ║
  460. ║ -- Mijn broer is onhebbelijk.                                              ║
  461. ║ Misschien ben jij wel onhebbelijk.                                         ║
  462. ║ -- Ik houd van reizen.                                                     ║
  463. ║ Daar wil ik meer over weten.                                               ║
  464. ║ -- Ik was laatst in de Belgische plaats Namen.                             ║
  465. ║ Namen interesseren me niet.                                                ║
  466. ╚════════════════════════════════════════════════════════════════════════════╝
  467. U voelt 'm al: Dit nepprogramma kiest willekeurig uit nietszeggende zinnetjes
  468. om de conversatie gaande te houden als het geen zinsdelen kan vinden die het
  469. kan verweven in quasi-intelligente antwoorden. Vaak worden antwoorden met
  470. verweven zinsdelen uit de invoer totaal verkeerd gebruikt.
  471. Bij het programmeren van deze en andere programma's die uitbundig van rando-
  472. mizers gebruik maken, moet vele, vele malen de opdracht:
  473. X=INT(RND*x)+1
  474. worden gebruikt. Dat kan ook eenmalig gebeuren met een weinig gebruikte maar
  475. zeer nuttige zelf te maken functie. GWBASIC voorziet in het zelf maken van
  476. functies en het later aanroepen van die functies met: DEF FN en FN. Evenals
  477. RANDOMIZE wordt een DEF FN bovenin de programmalijst geplaatst. De interpreter
  478. hoeft er slechts één keer voorbij te komen.
  479. Een DEF FN kan van alles zijn maar in ons geval kunnen we van te voren bepalen
  480. dat deze 'user DEFined FuNction' een randomizer gaat bevatten. Wanneer we de
  481. volgende zelf te maken function eenmalig en aan het begin van het programma
  482. declareren:
  483. DEF FN X=INT(RND*99)+1
  484. dan kunnen we hem in het werkende programma naar hartelust aanroepen met:
  485. FN X.
  486. De variabele X zal dan elke keer een willekeurig getal tussen 0 en 99 bevatten.
  487. U kunt dit niet in de directe modus van GWBASIC uitproberen. Een DEF FN in de
  488. directe modus geeft als foutmelding: Illegal direct. Daarom een piepklein
  489. programmaatje om dit eens nader te bekijken:
  490. ┌───────────────────────────┐
  491. │ 10 RANDOMIZE -TIMER       │  Tik deze oefening in GWBASIC en geef RUN. U 
  492. │ 20 DEF FN X=INT(RND*99)+1 │  krijgt kolomsgewijs net zo veel willekeurig ge-
  493. │ 30 WHILE INKEY$=""        │  kozen getallen onder de 100 als u maar hebben
  494. │ 40   PRINT FN X,          │  wilt. Voor verdeling in kolommen zorgt de kom-
  495. │ 50 WEND                   │  ma die in regel 30 na X als scheidingsteken fun-
  496. └───────────────────────────┘  geert. Het genereren van de willekeurige getal-
  497. len houdt pas op wanneer u op een toets drukt. Dit is het gevolg van de
  498. WHILE/WEND die op een intoetsing wacht. Tussen de dubbele aanhalingstekens in
  499. regel 20 staat niets; geen spatie dus! Als gevorderde kunt u toch lezen wat er
  500. staat? ZOLANG (WHILE) er geen toets ("")  wordt ingedrukt, druk het in DEF FN
  501. gedeclareerde random getal (FN X) af (WEND).
  502. Op de niet altijd even simpele maar toch wel erg machtige DEF FN komen we
  503. verderop in de cursus nog uitgebreid terug.
  504.  
  505. Randomizers in ON x GOTO
  506.  
  507. Tussen neus en lippen door leren we nu even een handig techniekje dat door
  508. velen vaak wordt gebruikt in een menugestuurd programma. Veel programma's
  509. beginnen met een overzichtelijk menu van opties of keuzemogelijkheden.
  510. Bijvoorbeeld:
  511.  
  512.                               ┌───────────┐
  513.                     ╔═════════╡ HOOFDMENU ╞═════════╗
  514.                     ║         └───────────┘         ║
  515.                     ║   1 - Zoeken op trefwoorden   ║
  516.                     ║   2 - Trefwoorden sorteren    ║
  517.                     ║   3 - Documenten invoeren     ║
  518.                     ║   4 - Systeemoverzicht        ║
  519.                     ║   5 - Statistiek              ║
  520.                     ║   6 - Einde programma         ║
  521.                     ■═══════════════════════════════■
  522.                     │  U kiest: 1, 2, 3, 4, 5 of 6  │
  523.                     └───────────────────────────────┘   
  524.  
  525. Als dit menu op het scherm staat verwacht de programmeur dat de gebruiker nu
  526. de cijfertoetsen 1 tot en met 6 gaat indrukken. Alle andere intoetsingen worden
  527. afgevangen. Het programma ontvangt de intoetsing en daarmee de keuze van de
  528. gebruiker in een numerieke variabele uit, bijvoorbeeld:
  529. 400 INPUT K
  530. 410 IF K<1 AND K>7 THEN BEEP:GOTO 400
  531.  
  532. Wanneer in de variabele K een geldige menukeuze staat, gaat het programma
  533. verder. Nu moet de programmeur zorgen dat het programma wegspringt naar het
  534. deel in het programma waar de bestelde functie begint. Hij heeft zijn programma
  535. netjes gestructureerd opgezet. Zoeken op trefwoorden begint op regel 1000,
  536. Trefwoorden sorteren op regel 2000, Documenten invoeren op 3000,
  537. Systeemoverzicht op 4000, Statistiek op 5000 en Einde programma op 6000.
  538. De programmeur mag nu afzien van een constructie waarin komt te staan:
  539. IF K=1 THEN GOTO 1000
  540. IF K=2 THEN GOTO 2000
  541. ...enzovoort. In plaats daarvan beschikt hij over de functie:
  542. ON [variabelenaam] GOTO [regelnummer].
  543. De verkeersregeling gaat na regel 410 dan als volgt in zijn werk:
  544.  
  545. 420 ON K GOTO 1000, 2000, 3000, 4000, 5000, 6000
  546.  
  547. In een programma dat de schijn wil ophouden dat het over iets beschikt dat doet
  548. denken aan de menselijke vrije wil, kunnen we de ON x GOTO constructie
  549. natuurlijk ook gebruiken met een randomizer. Stel dat in een simulatieprogramma
  550. in 'willekeurige' volgorde continue bepaalde delen van het programma moeten
  551. worden afgewerkt. Laten we aannemen dat die geroepen stukken netjes zijn onder-
  552. gebracht in delen. Schematisch geprogrammeerd ziet dat er zo uit:
  553. We hebben gebruik gemaakt van DEF FN  ┌───────────────────────────────────────┐
  554. functie in regel 20 om in regel 30    │ 10 CLS:KEY OFF:RANDOMIZE TIMER        │
  555. uit FN X steeds een andere waarde     │ 20 DEF FN X=INT(RND*5)+1              │
  556. voor de numerieke variabele X te ver- │ 30 ON FN X GOTO 60, 70, 80, 90, 100   │
  557. krijgen. De ON in regel 30 doet braaf │ 40 PRINT X                            │
  558. wat we willen. Omdat in X telkens een │ 50 REM Hieronder de 5 delen           │
  559. 1, 2, 3, 4 of 5 komt te staan, wordt  │ 60 PRINT "Dit is deel 1":GOTO 110     │
  560. de programmabesturing telkens op een  │ 70 PRINT "Dit is deel 2":GOTO 110     │
  561. ander punt in het programma voortge-  │ 80 PRINT "Dit is deel 3":GOTO 110     │
  562. zet. We kunnen dat op twee manieren   │ 90 PRINT "Dit is deel 4":GOTO 110     │
  563. controleren. In regel 40 wordt X ter  │ 100 PRINT "Dit is deel 5              │
  564. controle op het scherm gezet en in de │ 110 PRINT "Toets voor vervolg"        │
  565. programmadelen wordt weergegeven om   │ 120 WHILE INKEY$="":WEND:GOTO 30      │
  566. welk programmadeel het op dat moment  └───────────────────────────────────────┘
  567. gaat. U ziet trouwens ook waarom in regel 100 geen doorverwijzing naar regel
  568. 110 nodig is; het is de regel die in de programmaflow volgt op regel 100.
  569. Dit testprogramma gedraagt zich, evenals tal van andere als een perpetuum
  570. mobile. De STOP of END ontbreekt. Stop dit programma met Shift+Break.
  571.  
  572. Muzikale grapjes met randomizers
  573.  
  574. Randomizers kunnen de fantasie van de programmeur enorm prikkelen. Sterker nog:
  575. door te stoeien met functies en randomizers kun je tot ontdekkingen komen. Je
  576. kunt tot dingen komen die nog nooit eerder zijn bedacht. Dat geldt bijvoorbeeld
  577. voor grafische en geluidseffecten.
  578. We hebben het muziek- en geluidsgedeelte van GWBASIC nog niet gehad maar niets
  579. hoeft ons er van te weerhouden er onze randomizers op los te laten. Opgemerkt
  580. is al dat randomizers de illusie van kunstmatige intelligentie kunnen opwekken.
  581. Ze kunnen ook de illusie van kunsmatige creativiteit oproepen. Ik heb dat
  582. geprobeerd met onderstaand programma waarin ik de PC muzikale akkoorden laat
  583. genereren. Daarbij is uitgegaan van een simpel principe, namelijk dat muziek
  584. voor een belangrijk deel op herhaling van voorgaande akkoorden maar dan met
  585. verschillende toonhoogte is gebaseerd.
  586. En warempel... dit principe hanterend worden er geluiden ten gehore gebracht
  587. die meestal sterk aan gecomponeerde muziek doen denken.
  588. ╔════════════════════════════════════════════════════════════════════════════╗
  589. ║10 CLS:KEY OFF:RANDOMIZE TIMER                                              ║
  590. ║20 M$="ABCDEFG...."                                                         ║
  591. ║30 DEF FN X=INT(RND*2)+12                                                   ║
  592. ║40 DEF FN Y=INT(RND*11)+1                                                   ║
  593. ║50 DEF FN H=INT(RND*2)+2                                                    ║
  594. ║60 MUZIEK$=""                                                               ║
  595. ║70 FOR A%=1 TO FN X                                                         ║
  596. ║80   Z$=MID$(M$,FN Y,1)                                                     ║
  597. ║90   MUZIEK$=MUZIEK$+Z$                                                     ║
  598. ║100  IF A%/6=INT(A%/6) THEN GOSUB 180:MUZIEK$=MUZIEK$+"MB"+Z$:PRINT MUZIEK$ ║
  599. ║110  IF A%/7=INT(A%/7) THEN GOSUB 190:MUZIEK$=MUZIEK$+"ML"+Z$:PRINT MUZIEK$ ║
  600. ║120 NEXT A%                                                                 ║
  601. ║130 IF LEFT$(MUZIEK$,1)="." THEN MUZIEK$="":GOTO 30                         ║
  602. ║140 GOSUB 180                                                               ║
  603. ║150 PRINT:PRINT MUZIEK$                                                     ║
  604. ║160 MUZIEK$=MUZIEK$+"MLL4EDEGAGFEDMNL2C...C."                               ║
  605. ║170 PLAY MUZIEK$:END                                                        ║
  606. ║180 Z$="O"+STR$(FN H)+MUZIEK$:RETURN                                        ║
  607. ║190 Z$="L"+STR$((FN H)+2)+MUZIEK$:RETURN                                    ║
  608. ╚════════════════════════════════════════════════════════════════════════════╝
  609.  
  610. Print dit programma uit (gebruik desnoods <Shift/Print Scrn>) en luister. U
  611. zult het met me eens zijn dat er iets 'herkenbaars' in de akkoorden zit, maar
  612. over de vraag of dit 'muzikaal' is of 'mooi' mag u van mening verschillen.
  613. Merk op dat in de regels 30 tot en met 50 DEF FN is gebruikt voor de
  614. randomizers die elders in het programma in actie komen.
  615. Voor muziek wordt in GWBASIC het commando PLAY gebruikt. Dit is een nogal
  616. veelzijdig commando dat eigenlijk verspild is aan het goedkope en vaak ook
  617. kwalitatief slechts luidsprekertje waarmee PC's zijn uitgerust. In eerste
  618. aanleg was het luidsprekertje alleen bedoeld om piepjes van SOUND en BEEP
  619. hoorbaar te maken. PLAY verwacht een tekststring en het aardige is natuurlijk
  620. dat zo'n tekststring willekeurig kan worden gemaakt door het gebruik van
  621. randomizers.
  622. Willkeurige getallen die door RND() worden gemaakt moeten voor de tekststring
  623. naar tekst worden omgezet. Dit gebeurt in de regels 180 en 190 met de functie:
  624. STR$(numerieke variabele). STR$ is dus het omgekeerde van VAL(tekst) waarmee
  625. van een getal in de vorm van tekst een echt numeriek en verwerkbaar getal wordt
  626. gemaakt.
  627. Dat was het weer. Oefen vooral veel met het geleerde en bedenk zelf varianten.
  628.  
  629. Ik zie u over een paar maanden graag weer terug....
  630.  
  631.               (Deze cursus wordt voortgezet in RUN Flagazine)
  632.  
  633.  
  634. ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  635.  
  636.  
  637.  
  638.  
  639.