Het is pakweg 2 jaar geleden dat ik met deze serie ben begonnen en daarmee de missie om jou, mijn trouwe lezer te leren programmeren. In mijn tweede blog hebben we samen een spel geprogrammeerd, maar omdat we samen tot de conclusie waren gekomen dat OrcadoInvaders iets te hoog gegrepen was voor je eerste introductie tot programmeren, was dat een tekstgebaseerd_computerspel spel. Herinner je je het weer? Ondertussen heb je al heel wat meer geleerd en ook zeker niet onbelangrijk is de P5js webeditor uitgekomen. Ik vermoed dat ik P5 wel eens eerder heb genoemd; het is de JavaScript bibliotheek die ik gebruik in de meeste van mijn code voorbeelden, waaronder TicTacToe en OrcadoInvaders. P5 is gebaseerd op processing.org een organisatie die net als ik, de ambitie heeft om meer mensen te leren programmeren. Maar als ik jou was zou ik mijn blog lezen, dan krijg je tenminste ooit nog uitgelegd wat 3-waarden-logica nou precies is en hoe dat werkt.
Maar niet vandaag. Vandaag gaan we weer samen een spelletje programmeren, maar nu met graphics. Na wat overwegingen wat nou een goed spel zou zijn om samen te maken is de keuze gevallen op ‘hooghouden’, er is een heel sub genre van dit spel op bijvoorbeeld funny games, maar volgens mij zijn die allemaal in flash, en gezien flash vanwege de vele veiligheidsproblemen binnenkort in geen enkele browsers meer draait, zijn dat allemaal niet echt goede voorbeelden. Gelukkig is het spelletje heel simpel; je hebt een bal, die valt naar beneden, als je er op klikt ‘schiet’ je hem omhoog, het doel is om de bal zo lang mogelijk hoog te houden voor dat die valt. We gaan dat doen in de hiervoor genoemde P5js webeditor.
Zodra je de webeditor opent zie je links de code en als je op de play-knop drukt zie je rechts het resultaat van de code. Klik maar eens op play, je ziet rechts een vierkant verschijnen. De code die daar voor zorgt staat links onder setup en draw. Setup wordt aan het begin 1x uitgevoerd en draw elke seconde 60x. In setup wordt een canvas aangemaakt van 400 breed en 400 hoog. Ik denk graag zo groot mogelijk dus dat mag je direct veranderen in:
createCanvas(windowWidth, windowHeight);
Hiermee maken we het canvas (waar we op gaan schilderen) zo breed en hoog als in het scherm past. In draw krijgt het canvas een grijze achtergrond kleur, dat is wat saai en aangezien hooghouden een voetbal spelletje is maken we dat meteen gras groen:
background(0,180,0);
Als het goed is, is het hele rechtervlak nu groen. Mocht dat nou niet zo zijn of als je ergens verderop de draad kwijt bent, dit is het eind resultaat, kan je spieken wat er mis is gegaan.
Ok, het gras is er, nu een bal. Daarvoor gaan we een object maken (ik heb je niet voor niets leren objectificeren). P5 ondersteunt om hiervoor een apart bestand aan te maken, en dat is eigenlijk mooier, maar voor de eenvoud hou ik het even in één bestand. Eerst maken we een bal functie die als ons object gaan dienen:
function Bal(){
}
De computer moet weten hoe groot de bal is en waar die op het scherm moet worden getekend, dus daarvoor maken we variabelen aan in de functie.
function Bal() { this.diameter = 50; this.x = 0; this.y = 0; }
En de bal moet getekend worden, daarvoor maken we een functie aan:
function Bal() { this.diameter = 50; this.x = 0; this.y = 0; this.teken = function() { ellipse(this.x, this.y, this.diameter, this.diameter); }; }
In die functie staat nu dat we een ellips tekenen op coördinaten (0,0) en met de breedte en hoogte van de diameter. Aangezien een ellips met gelijke breedte en hoogte een perfecte cirkel is, gaat dit een cirkel tekenen. Om de bal te laten tekenen moeten we dit aanroepen in de draw functie. Eerst maken we de bal variabele aan in setup
function setup() { createCanvas(windowWidth, windowHeight); bal = new Bal(); }
Zodat we daarna in de draw functie hem kunnen laten tekenen.
function draw() { background(0,180,0); bal.teken() }
Er verschijnt nu een witte kwart cirkel in de linker bovenhoek, laten we hem even in het midden zetten voor het gemak, verander daarvoor in het bal object de x en y waarden respectievelijk windowWidth/2 en windowHeight/2. De bal staat nu midden in het scherm.
Nu gaan we wat leven in de bal brengen. Dat gaan we doen door zwaartekracht toe te voegen. In een hele volwassen versie van dit spel gebruiken we daarvoor een physics engine, dat is software die natuurkundige krachten nabootst, daarmee kunnen we rekening houden met het gewicht van de bal, de luchtweerstand, de gravitatie constante en dat soort dingen. Dat is echter overkill voor wat we aan het doen zijn, we gaan de bal een verticale snelheid geven, en de zwaartekracht verhoogt die snelheid neerwaarts. Eerst maar verticale snelheid aan het bal object toevoegen:
this.verticalesnelheid = 0;
En we maken een zwaartekracht functie die die snelheid beïnvloed en dan ook de y coördinaat van onze bal:
this.zwaartekracht = function() { this.verticalesnelheid += 0.2; this.y += this.verticalesnelheid; };
Dan alleen nog zorgen dat de zwaartekracht toegepast wordt elke keer als we de bal tekenen, daarvoor zetten we onder bal.teken(), bal.zwaartekracht() en voilá de bal valt naar beneden. Klein probleempje, de bal verdwijnt al snel onder de grond. Dat is natuurlijk niet goed. We zullen in onze super eenvoudige versie van een physics engine de grond de bal laten stuiteren door een opwaartse kracht toe te voegen als de grond geraakt wordt, soort derde wet van Newton zeg maar:
if (this.y + this.diameter / 2 > windowHeight) { this.y = windowHeight - this.diameter / 2; // zorg dat de bal niet vastkomt in de grond this.verticalesnelheid *= -0.8; } else { this.verticalesnelheid += 0.2; }
En hup, de bal stuitert, het verdient geen nobelprijs voor de natuurkunde, of zelfs maar een voldoende op een middelbare school toets, maar dit is soort van hoe zwaartekracht werkt, en goed genoeg voor ons spelletje.
Nu moeten we de bal nog hoog kunnen houden, dat gaan we doen door er op te klikken. P5 heeft een ingebouwde functie om muisklikken te herkennen, alle functies die P5 ondersteunt kan je vinden in de referentie. We gaan gebruik maken va de touchStarted functie, in mijn ervaring werkt het dan goed met zowel muizen als met aanraakgevoelige schermen zoals mobiele telefoons.
function touchStarted() { bal.verticalesnelheid -= 20; return false; }
Die return false wordt aangeraden in de reference dus dat doen we maar braaf. Het zal je inmiddels opgevallen zijn dat een negatieve snelheid de bal naar boven brengt en een positieve snelheid de bal naar beneden. Dat komt omdat het y-coördinaat 0 helemaal boven in het scherm ligt en het y-coördinaat met de schermhoogte helemaal onderin het scherm.
Dit werkt, alleen het maakt nu niet uit waar je klikt, dat maakt het een suf spelletje. Dit kan je nu makkelijk winnen door in je console met een interval muisklikken te genereren (je hebt immers leren automatiseren). We moeten dus kijken of er op de muis wordt geklikt. We hebben gelukkig ook de x en y coördinaten van de muis (of vinger) dus we hoeven alleen maar te controleren of de afstand tussen de muis en de bal gelijk of kleiner is dan de straal van de bal. Dit is normaal het moment dat ik me realiseer dat ik meer de straal dan de diameter gebruik en de code daar op aanpas, maar ik begin inmiddels de spreekwoordelijk prangende ogen van mijn redactrice in mijn rug te voelen gezien ik al ver over het aantal streefwoorden per blog ben (ik geef persoonlijk de ellenlange zinnen die ik maak de schuld, alsof ik een kommarecord probeer te verbreken…), dus dat mag je straks zelf doen. Dat is overigens ook de reden dat ik onze gezamenlijke flashback naar middelbare school wiskunde en de stelling va Pythagoras) oversla (ja, daar hadden we nu eindelijk een keer ‘in het echt’ gebruik van kunnen maken) en gebruik maak van de euclidische afstandsfunctie die in P5 zit ingebakken:
if (dist(bal.x, bal.y, mouseX, mouseY) <= bal.diameter / 2) { bal.verticalesnelheid -= 20; }
Op zich werkt het nu, maar het is wel een erg verticale aanpak, dat is wel heel erg agile, maar voor het spelletje niet echt leuk. Probeer zelf eens toe te voegen dat als je de bal links van het midden raakt hij naar rechts gaat en andersom (en vergeet niet van de zijkant muren te maken zodat je de bal niet kwijt raakt). Ik heb het in het eindresultaat zit, maar ik geloof dat je dit zonder spieken kan!
Als je dat gedaan hebt is het tijd voor het testen, dan blijkt dat het spelletje misschien iets te uitdagend is geworden. Met een klein beetje tweaken aan de gebruikte getallen en een scoreteller toe te voegen hebben we in de basis het spelletje. Dat valt echter ook buiten de scope van dit blog, maar dat moet je niet laten weerhouden om het zelf te doen en hoewel het het beste is om dat zonder hulp op te lossen hoeveel moeite je dat kost, kan ik me herinneren hoe frustrerend dat kon zijn toen ik net begon met programmeren, dus ook daarvoor een spiekbriefje.
Leren programmeren automatiserenIk heb lang getwijfeld over dit blog, voor de beste uitleg zou ik jullie namelijk mee willen nemen in de wereld van autohotkey (https://www.autohotkey.com/) dat is een programma waarmee je op Windows heel makkelijk een heleboel dingen kan automatiseren. Het gebruikt echter zijn eigen programmeer taal, en ik doe mijn best om al het programmeren dat ik samen met jullie doe, in javascript te doen, zodat jullie makkelijk mee kunnen blijven doen. Een aardige uitleg waarom ik jullie autohotkey wilde laten zien en waarom ik het niet heb gedaan, is dit filmpje van Tom Scott.
Een nadeel van javascript voor het uitleggen van automatisering is dat je browser veilig is gemaakt. Elke keer dat je naar een andere pagina gaat wordt je console opnieuw gestart. Dat is maar goed ook, want als dat niet zou gebeuren zou je daar vreselijk misbruik van kunnen maken. Maar dat betekent dus dat je alleen maar dingen in je browser kunt automatiseren die binnen hetzelfde scherm gebeuren. Wat je dan nog wel kunt doen is data van een andere website ophalen via een API en dat vervolgens gebruiken tijdens je automatisering. Dus mocht je straks de smaak te pakken hebben, pak mijn vorige blog er nog een keer bij.
Maar laten we iets simpeler beginnen. Het idee van iets automatiseren is dat je een taak die saai is of die je vaak moet doen, door de computer kan laten doen. Dat klinkt heel aanlokkelijk, maar het is niet altijd de moeite waard. Xkcd heeft een tabel waarin je kunt opzoeken of het zinvol is om iets te optimaliseren door het te automatiseren: (bron: xkcd.com)
Als je er dan ook nog een andere xkcd bij pakt over automatiseren schetst dat een plaatje waarin het automatiseren van taken niet altijd de heilige graal is die het soms lijkt te zijn:
Dat neemt niet weg dat je soms door iets te automatiseren enorm veel werk door een computer kan laten doen in plaats van mensen, of de computer taken kan laten doen waar je zelf eigenlijk geen zin in hebt. Uit ervaring blijkt dat totaal fictieve onrealistische voorbeelden vaak het beste zijn, het zet de lezer namelijk aan tot zelf een realistisch scenario te bedenken. Bovendien is het voor mij het minste werk en dat is toch een beetje waar dit blog over gaat. Dus we doen een raar hypothetisch voorbeeld.
Stel je doet een online survey over een video en om te zorgen dat je de video blijft kijken en dat je niet bij je computer wegloopt moet je van de website elke 20 seconden op een knop drukken, dat reset een timer,als die timer niet op tijd gereset wordt is je aandacht er schijnbaar niet genoeg bij en krijg je niet betaald. De ethische oplossing is elke 20 seconden op de knop drukken, de makkelijkste manier, automatiseer het drukken op de knop. Hier is de site: https://undercover.horse/automatiseren/resettimer.htm
Het automatiseren van deze taak is vrij eenvoudig. Wat we willen doen is elke X seconde op de knop drukken, omdat te doen moeten we eerst weten hoe de knop heet, daarvoor kijken we naar de broncode van de site. Daarin vinden we:
<button id="btn" onclick="set20()">reset de timer</button>
Dat is makkelijk, de knop heeft een id attribuut. Javascript heeft een manier om een object te selecteren bij zijn id:
document.getElementById()
We geven het id “btn” mee en dan gebruiken we de click functie om er op te klikken:
document.getElementById("btn").click()
Dat is 1 klik, we willen het echter elke X seconden, omdat te doen kunnen we er een interval van maken:
setInterval('document.getElementById("btn").click()',5000);
Deze code zorgt er voor dat er elke 5000 milliseconden (gelijk aan 5 seconden) op de knop wordt geklikt. Als je dat in je console plakt ben je klaar, de timer komt niet meer voorbij 15.
Dit is een truc die je op elke pagina toe kan passen die een knop heeft waar je op moet klikken die niet de hele pagina ververst. Ander voorbeeld: stel je voor je wilt een kaartje kopen, om dat te doen moet je op de bestelknop op een pagina klikken. Deze wordt echter pas klikbaar op een bepaald moment. Je kan geduldig op de site wachten en toeslaan zodra de knop beschikbaar wordt, of je blijft er constant op klikken en loopt er bij weg. Hier is een demo pagina: https://undercover.horse/automatiseren/klikzsm.htm
Kleine tegenvaller, als we naar de knop kijken zien we dat hij geen id heeft:
setInterval('document.getElementById("btn").click()',5);
Dat is echter makkelijk op te lossen, als je in je console zit kan je naar het tabblad elements waar je de html van de pagina ziet staan. Als je vervolgens met je rechtermuisknop op de html code van de button klikt, kan je kiezen voor “edit as HTML” en dan kan je er zelf gewoon id=”btn” bij zetten. (Je moet buiten het edit-blokje klikken om het te bevestigen) Omdat we zo snel mogelijk willen zijn zetten we de interval waarde wat lager:
setInterval('document.getElementById("btn").click()',5);
Nu gebeurt het elke 5 milliseconden, daar kan je zelf nooit tegenop klikken. In het huidige voorbeeld is het alleen wel zo dat je al die acties binnen 20 seconden moet doen anders is de knop al beschikbaar. Als alternatief kan je ook de knop zoeken door alle knoppen te zoeken en dan de eerste uit de lijst te kiezen:
setInterval('document.getElementsByTagName("button")[0].click()',5);
Het klikken op een knop is handig, maar vaak moet je meer op een pagina doen dan alleen een knop indrukken. Gelukkig kan je met javascript een heleboel van die dingen gewoon doen. Neem bijvoorbeeld een pagina met een formulier zoals deze: https://undercover.horse/automatiseren/formulier.htm
Stel we willen blauw een heel aantal keer kiezen. Dan kunnen we het formulier een aantal keer achter elkaar invullen. Of we automatiseren het:
let y = function () { document.getElementsByTagName("input")[0].value = "Daan"; document.getElementsByTagName("input")[1].value = "Veraart"; document.getElementsByTagName("input")[4].checked = true; document.getElementsByTagName("button")[0].click(); } setInterval(y, 100);
Dit lijkt allemaal vrij triviaal, maar dit zijn dan ook nog maar hele simpele voorbeelden, die je allemaal in je browser kunt uitvoeren. De essentie is echter dat je een taak opsplitst in kleine subtaakjes en dan kijkt of je die met een stukje code kan oplossen. Dat is iets wat je bij programmeren eigenlijk heel vaak gebruikt. Alleen dan in de vorm van helper functies. Als je en stuk code meerdere keren in je applicatie hebt staan, dan kan je beter van dat stuk code een functie maken. Elke keer dat je dan dat stuk code nodig hebt, automatiseer je dat door in plaats van dat stuk code te typen, de functie aan te roepen. Bijkomend voordeel is dat als je iets aanpast in dat stuk code, het meteen op alle plekken in je programma aangepast is.
Het leukste van iets automatiseren is kijken hoe de computer voor je aan het werk is. Zo heb ik laatst het verplaatsten van Vimeofilmpjes naar Youtube geautomatiseerd. Als je het dan opstart (de foutmelding oplost waar je tegenaan loopt (voor degene die het filmpje van Tom Scott hebben gezien, het was een beetje een bodge) en nog een keer opstart) en je ziet dat hij logregel voor logregel braaf bezig is, en dan op Youtube het filmpje ziet verschijnen met dezelfde titel en omschrijving als dat het op Vimeo heeft, dat geeft een voldaan gevoel. Het is wel een klein beetje zonde dat we de tijd die we gewonnen hebben met automatiseren besteden aan het kijken naar hoe de computer het werkt doet, die hadden we beter kunnen besteden aan het uitleggen van drie waarde logica… Misschien moet ik dat maar eens proberen te automatiseren.
Leren programmeren APIreciërenBelofte maakt schuld, vandaag hebben we het over APIs. API is de afkorting voor application programming interface. Een interface gebruik je om te communiceren met een applicatie. De bekendste vorm daarvan is de GUI (graphical user interface); als je een applicatie opstart op je computer of telefoon dan is wat je ziet de GUI, het is de manier waarmee jij met de applicatie kan communiceren. Je kijkt nu naar de GUI van je browser, je kan communiceren door bijvoorbeeld te scrollen en je browser beantwoord door de pagina naar boven of beneden te verschuiven. Voor mensen is een grafische interface handig, een plaatje zegt immers meer dan 1000 woorden. Voor een computer is dat anders, die communiceert een stuk makkelijker met alleen tekst.
Nu vraag je je misschien af, hoe vaak gebeurt het nou dat een applicatie met een andere applicatie communiceert? Dat ligt een beetje aan je definitie. Als je het heel sec bekijkt, communiceert jouw browser nu met een server om deze tekst op te halen, met de driver van je scherm om het op je scherm te laten zien, met je muis of touchscreen om je invoer te registreren enzovoort. Er is dus heel veel interactie, maar we gaan het over een heel klein gedeelte hebben, want ik ratel altijd al veel te veel en anders komt er geen eind aan.
We gaan het hebben over WebAPIs, dat is waar de meeste mensen het over hebben als ze API zeggen, dus dat is wel zo makkelijk. WebAPIs zijn functies die je aan kan roepen via het internet die informatie terug geven van de applicatie waar ze voor geschreven zijn. Dat is vrij abstract, dus het is zoals altijd tijd voor een voorbeeld. Stel je wilt iemand 3-waarden logica uitleggen… Wacht ik heb jullie nog steeds geen 3-waarden logica uitgelegd en ik zit alweer boven de 300 woorden en ik ben nog niet eens begonnen met het voorbeeld, we doen wel iets anders… Stel je wilt iemand Engels leren, maar aangezien je het wel leuk wilt houden besluit je een spelletje te maken.
Het spelletje is heel simpel je pakt een plaatje, je geeft er een aantal woorden bij en dan moet er geraden worden welk woord er bij het plaatje hoort. Klinkt simpel toch? Als je er nog een beter beeld bij wilt hebben kan je het hier spelen. Ik heb het basic gehouden dus er is bijvoorbeeld geen score, maar ik heb wel een hint optie toegevoegd, want het spelletje is zonder soms niet te doen.
Voor het spelletje heb ik 2 dingen nodig, een heleboel Engelse woorden en een heleboel bijpassende plaatjes. Die kan ik natuurlijk zelf samenstellen, maar er zijn applicaties op het internet die dit al hebben dus die kan ik ook gebruiken. In dit voorbeeld gebruik ik de wordnik.com API. Wordnik is een online woordenboek en daar mag je gratis gebruik van maken. Tenminste als je het niet te zot maakt, om dat een beetje in de gaten te houden heb je een sleutel nodig om gebruik te maken van hun API, deze kan je gratis aanvragen en daarmee mag je dan een 100 aantal aanvragen per uur doen. Dus als je het spelletje heel enthousiast speelt dan zal hij het op een gegeven moment een uur niet doen. Wil je meer aanvragen doen, dan moet je daar voor betalen. De andere API is die van imgur.com, zelfde idee maar dan een plaatjes site.
We beginnen met het ophalen van 12 willekeurige Engelse woorden. Dat doen we met een API call. De Wordnik API heeft daar deze functie voor. Die roepen we aan met een URL: https://api.wordnik.com/v4/words.json/randomWords?hasDictionaryDef=true
&minCorpusCount=50
&maxCorpusCount=-1
&minDictionaryCount=20
&maxDictionaryCount=-1
&minLength=6
&maxLength=6
&limit=12
&api_key=APISLEUTEL. Deze URL vertaalt ongeveer naar: Op de v4 versie van de api.wordnik.com applicatie roepen we van de words.json functies de functie randomWords aan met een aantal argumenten. Voor de leesbaarheid heb ik alle argumenten op een eigen regel gezet. Alles achter het vraagteken zijn de argumenten waarmee ik woorden terug laat komen van 6 letters die niet super obscuur zijn. Ja, trouwe lezer, daar staat words.json. Tegenwoordig gebruikt het leeuwendeel van de WebAPIs JSON strings om te communiceren. Zoals je in het vorige blog hebt kunnen lezen zijn dat javascript objecten en dat is makkelijk want ik programmeer dit spelletje in javascript.
In de code ziet dit er zo uit:
async function laadApi() { //code niet belangrijk voor de uitleg// let wordnik_res = await fetch(url1+x); woorden = await wordnik_res.json();
Voor wie al sinds blog 1 enthousiast mee aan het programmeren is valt het waarschijnlijk op dat er ineens iets voor function staat. Het woord async geeft aan javascript aan dat er dingen asynchroon gaan gebeuren. Dit is volgens mij ook de eerste keer dat ik het gebruik, voor nu ga er maar even vanuit dat het hetzelfde is als var. De functie fetch haalt gegevens uit vanuit de url die je meegeeft. Ik geef de url van hierboven mee met mijn API sleutel. Normaal gesproken als je fetch afvuurt gaat daarna direct de volgende regel van je code af. De fetch functie kan even duren, maar daar wil je browser als het niet hoeft niet op wachten. Wij kunnen echter niet verder voor we die data hebben, dus we zeggen met het woord await tegen de browser dat hij moet wachten tot fetch klaar is voordat hij verder mag gaan met de rest van de code. Hetzelfde geldt voor de json functie, die het resultaat omzet in een javascript object. Na deze twee regels code is ‘ woorden’ dit:
[{ "id": 234698, "word": "Yankee" }, { "id": 16370, "word": "beggar" }, { "id": 31968, "word": "chorus" }, { "id": 47154, "word": "dental" }, { "id": 129143, "word": "magpie" }, { "id": 133965, "word": "miller" }, { "id": 81088, "word": "patrol" }, { "id": 70598, "word": "reform" }, { "id": 73645, "word": "severe" }, { "id": 228047, "word": "vector" }, { "id": 81488, "word": "volley" }, { "id": 74147, "word": "worthy" }]
Tenminste, deze keer, elke keer dat we die code uitvoeren krijgen we andere woorden terug van Wordnik. Het is een lijst met 12 objecten, alle objecten hebben een id, een nummer waar we verder niets mee doen, en een word, het woord waar we naar opzoek waren.
Als we dat hebben zoeken we daar een plaatje bij:
woord = random(woorden).word let imgur_res = await fetch(url2+woord, { method: "GET", headers: { "Authorization": "Client-ID "+ clientID }}); plaatjes = await imgur_res.json()
Eerst pakken we een willekeurig woord uit de rij en dan geven we die mee aan de fetch voor Imgur. De Imgur API heeft de API sleutel niet in de URL staan, maar gebruikt een header voor die informatie. Een header is informatie die je browser meestuurt met een URL. Ook Imgur werkt met json dus we roepen met het resultaat de json functie aan om het bericht weer om te zetten naar een javascript object. De Imgur API geeft veel meer informatie terug dan alleen het plaatje. Zo geeft hij bijvoorbeeld ook terug hoe vaak er gereageerd is op dit plaatje, wie het plaatje heeft geüpload en wat de titel van het plaatje is. Waar we in eerste instantie in geïnteresseerd zijn is het plaatje zelf.
Tenminste als er een plaatje is. Het kan namelijk best zo zijn dat we van Wordnik een woord hebben gekregen waar Imgur helemaal geen plaatjes bij kan vinden. We moeten dus controleren of er wel plaatjes zijn, als dat zo is dan halen we de link uit het object en laten het plaatje zien waar de link naar verwijst. Vervolgens maken we linkjes van de 12 woorden die we van Wordnik hebben gekregen en zetten die er onder en het spelletje is af. Wil je de rest van de code ook zien, dan kan dat hier; ik heb er ook wat commentaar bij gezet.
Dat zijn dus WebAPIs, je vraagt aan een website gegevens door een specifieke URL aan te roepen en je krijgt die (meestal) in de vorm van een JSON string terug. Waarmee je dan vervolgens weer verder kan programmeren. WebAPIs zijn hip, dus als je denkt ‘dit is leuk!’ ik wil zelf ook wel met een API spelen, hier zijn een aantal voorbeelden van WebAPIs waar je mee kan praten.
Leren programmeren objectificerenVoordat ik dit blog ben gaan schrijven heb ik een aantal van mijn trouwe blog lezers toegezegd dat ik in mijn onophoudende missie om te laten zien dat programmeren geen magie is (voor de woordgrap is het belangrijk dat je ons wie-zijn-wij filmpje even kijkt) een blog zou schrijven over APIs. Terwijl ik daar mee bezig ben en aan het kijken wat ik daar voor uit wil leggen loop ik tegen JSON aan, wat staat voor JavaScript Object Notation. JavaScript is al wel een heel aantal keer voorbij gekomen, maar objects heb ik nog niet echt uitgelegd. In eerste instantie wilde ik een kleine paragraaf er aan wijden, maar dat voelde wat karig. Het alternatief was om het te noemen en dan later toe te lichten, maar ik moet ook ooit nog een keer 3-waarde logica uitleggen en het was eigenlijk logischer om dat eerst te doen. De APIs komen volgende keer (of de keer daarop) maar vandaag: object georiënteerd programmeren.
Misschien dat je de term object georiënteerd programmeren of de afkorting OO/OOP wel eens voorbij hebt zien komen. Object georiënteerd programmeren is een manier van programmeren, andere vormen zijn bijvoorbeeld functioneel, logisch of declaratief. De code die ik jullie tot nu toe heb laten zien is allemaal declaratief; het is een opsomming van constructies. Bij OOP ga je uit van objecten. Stel je maakt een spelletje zeg monopolie, dan is eigenlijk alles wat je vast kan houden een object: het bord, het geld, de pionnen, de kanskaarten, de straten etc. Als voorbeeld ga ik de dobbelstenen nemen, vooral omdat die vrij universeel in een heleboel andere spellen gebruikt worden.
Omdat ik geloof dat je moet blijven proberen om programmeren een beetje onder de knie te krijgen ga ik de objecten als een functie implementeren, dat is misschien niet de manier waarop de meeste tutorials het uitleggen, maar het zorgt er voor dat je de script snippets die ik straks laat zien zo in je browser console kunt plakken en uit proberen. Bovendien kan ik daarmee in een later blog heel makkelijk JSON uitleggen, maar dat is voor later. Laten we een object gaan maken.
Voor het maken van een object moeten we eerst bedenken wat de eigenschappen van dat object zijn. Voor een standaard dobbelsteen is dat een kubus met dus een 6 tal zijdes, met op die zijdes een aantal ogen oplopend van 1 t/m 6. Ook is het zo dat de som van het aantal ogen van de twee zijdes die het verst van elkaar afliggen 7 is, maar daar gaan we ons in het voorbeeld geen zorgen over maken. Eerst gaan we het aantal zijdes toevoegen.
var dobbelsteen = { aantalZijdes: 6 }
Dat was nog niet heel ingewikkeld toch? Laten we ook nog de ogen toevoegen, dat is een lijstje, dus dat zetten we tussen blokhaken.
var dobbelsteen = { aantalZijdes: 6, ogen: [1, 2, 3, 4, 5, 6] }
Als we dat in onze browser console gooien en we proberen vervolgens het 3de oog te laten zien ziet dat er zo uit:
var dobbelsteen = { aantalZijdes: 6, ogen: [1, 2, 3, 4, 5, 6] } dobbelsteen.ogen[2]; > 3
Dat komt een lijstje in JavaScript begint bij 0, dus als we het 3de element willen hebben moeten we het item uit het lijstje hebben met het nummertje 2. Nu hoor ik je denken, leuk, maar wat is hier nou het voordeel van boven:
var dobbelsteenZijdes = 6; var dobbelsteenOgen = [1,2,3,4,5,6];
Goede vraag, ik had hem zelf kunnen stellen. Een voordeel is dat je de variabelen mooi groepeert en die groepering vervolgens weer gemakkelijk ergens anders kunt gebruiken. Ook geeft het je code een gestructureerde logica, alles is een object en heeft eigenschappen. Maar een object kan meer dan alleen variabelen groeperen. Hij ondersteunt naast eigenschappen ook acties en in JavaScript zijn acties functies. Dobbelstenen moeten rollen, dus laten we een functie maken om dat te ondersteunen.
var dobbelsteen = { aantalZijdes: 6, ogen: [1, 2, 3, 4, 5, 6], dobbel: function () { return this.ogen[Math.floor(Math.random() * this.aantalZijdes)] } }
Nu kunnen we dobbelen door de functie aan te roepen als actie van de dobbelsteen:
dobbelsteen.dobbel(); > 4
Met monopolie heb je twee dobbelstenen en het aantal zetten dat je mag doen is het totaal van de twee dobbelstenen, dat is nu simpel te doen:
dobbelsteen.dobbel()+dobbelsteen.dobbel() > 7 dobbelsteen.dobbel()+dobbelsteen.dobbel() > 6
Bij de 7 gaat dit goed, bij 6 echter kan het zijn dat we dubbel hebben gegooid, namelijk 2x 3, als dat zo is mogen we nog een keer gooien, dus dat is wel belangrijk om te weten. Of een worp ‘dubbel’ is eigenlijk een eigenschap van beide dobbelstenen. Als beide dobbelstenen samen één eigenschap hebben, dan zijn ze eigenlijk samen voor ons ook een object dat bestaat uit 2 dobbelsteen objecten. Dat ziet er zo uit:
var dobbelstenen = { aantalDobbelstenen: 2, dobbelstenen: [dobbelsteen, dobbelsteen] }
Dat is wel heel vaak het woord dobbelsteen, en we mogen zelf de namen kiezen, dus laten we dit object worp noemen:
var worp = { aantalDobbelstenen: 2, dobbelstenen: [dobbelsteen, dobbelsteen] }
De worp heeft dan natuurlijk ook een actie dobbel, waar in beide dobbelstenen worden gedobbeld:
var worp = { aantalDobbelstenen: 2, dobbelstenen: [dobbelsteen, dobbelsteen], dobbel: function () { return this.dobbelstenen[0].dobbel() + this.dobbelstenen[1].dobbel() } }
Dit lost ons probleem nog niet op, maar we hebben nu de twee dobbelstenen samen, dus we kunnen nu kijken of er dubbel gegooid is, daarvoor voegen we een klein beetje extra logica toe:
var worp = { aantalDobbelstenen: 2, dobbelstenen: [dobbelsteen, dobbelsteen], dobbel: function () { let dobbel1 = this.dobbelstenen[0].dobbel(); let dobbel2 = this.dobbelstenen[1].dobbel(); let dubbelIndicator = "" if (dobbel1 == dobbel2) { dubbelIndicator = " dubbel!" } return dobbel1 + dobbel2 + dubbelIndicator } }
Dan nog even testen of het werkt.
worp.dobbel() > "6" worp.dobbel() > "11" worp.dobbel() > "8" worp.dobbel() > "4" worp.dobbel() > "12 dubbel!"
Aah ja daar is die, nou is het voor 12 natuurlijk altijd dubbel, maar we weten ook dat de 6 8 en 4 die we daarvoor hebben gedobbeld niet dubbel waren.
Nou zijn er ook dobbelstenen met een andere vorm en andere hoeveelheden ogen, in het spel D&D (Dungeons and Dragons) heb je verschillende dobbelstenen en daarmee bedoel ik dit soort dobbelstenen:
Een veelgebruikte is er één met 20 zijdes, die noem je d20 (dobbelsteen met 20 zijdes). We hebben nu het object dobbelsteen met 6 zijdes, maar we kunnen daar ook prima 20 zijdes van maken.
dobbelsteen.aantalZijdes = 20; dobbelsteen.ogen = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
Omdat we nu het bestaande object dobbelsteen hebben aangepast is dat ook meteen aangepast in het worp opject, dus als we nu de worp dobbelen kunnen we veel hogere getallen krijgen:
worp.dobbel() > "21" worp.dobbel() > "19"
Dat zijn objecten. Ergens in het begin zei ik dat dit waarschijnlijk niet de manier is waarop het in de meeste tutorials uitgelegd wordt. Wat ik nu heb laten zien is hoe je losse objecten aanmaakt door een variabele te vullen met JSON (JavaScript Object Notation.) Wat je meestal zult zien is dat er een model van een object wordt gemaakt, en dat je daar vervolgens versies van kan maken. Ik zal daar ook nog een voorbeeld van laten zien, maar daar ga ik iets sneller; ik heb het verzoek gekregen de blogs niet te lang te maken, en ik zit al weer op ruim 1000 woorden.
Wat we gaan doen is functie objecten maken, in plaats van JSON gebruiken we een functie om het object te defineren.
//standaard aantal zijdes is 6 function d(az = 6, o = []) { this.aantalZijdes = az; this.ogen = o; //als je alleen het aantal zijdes op geeft vullen we die met getallen if (o.length == 0) { for (let i = 1; i <= az; i++) o.push(i); } this.dobbel = function () { return this.ogen[Math.floor(Math.random() * this.aantalZijdes)] } }
Nu kunnen we simpel zeggen:
var d20 = new d(20);
En nu is d20 een dobbelsteen met 20 zijdes. Maar we kunnen ook een kleuren dobbelsteen maken met:
var dkleur = new d(6,["rood", "geel","groen","paars","oranje","blauw"] );
Als je dan dobbelt krijg je één van die kleuren terug.
dkleur.dobbel() > "blauw"
Dat kunnen we dan natuurlijk ook voor de worp doen:
function w(ds, aantal = 0) { //als er 1 dobbelsteen wordt meegegeven vul het lijstje met aantal stuks er van if (aantal > 0) { this.dobbelstenen = []; for (let i = 1; i <= aantal; i++) this.dobbelstenen.push(ds); } //anders gebruik de opgegeven dobbelstenen else this.dobbelstenen = ds; this.dobbel = function (show = false) { let totaal = 0; let resultaat = "" for (d of this.dobbelstenen) { let ogen = d.dobbel() totaal += ogen; resultaat += ogen + "+"; } resultaat = resultaat.substr(0, resultaat.length - 1); resultaat += "=" + totaal if (!show) resultaat = "" + totaal return resultaat; } this.dobbelShow = function () { return this.dobbel(true); } }
In D&D kan je een spreuk doen die 8d8 aan schade doet (Je moet dan 8 keer met een dobbelsteen met 8 zijdes rollen en het totaal optellen, Dungeons and Dragons ga ik verder niet uitleggen hoor, dat moet je maar een keer bingen), als we dat in onze object georiënteerde functies willen doen kans dat nu zo:
var d8 = new d(8); var w8x8 = new w(d8,8) w8x8.dobbelShow() > "3+1+8+8+7+3+7+6=43" w8x8.dobbel() > "30"
Dat zijn kort samengevat objecten. Ik heb het voorbeeld gegeven van dobbelstenen, maar deze techniek kan je voor vrijwel alles toepassen. Kleine tip: als je mensen of dieren objectificeert, zorg dan wel dat je dat alleen tijdens het programmeren doet, sommige dingen vertalen slecht naar de echte wereld.
Leren programmeren optimaliserenAls je weleens op je telefoon kijkt bij je apps welke wijzigingen er in al die updates zitten zie je meestal 3 dingen: bugfixes, tweaks en improvements. Bugfixes spreekt redelijk voor zich, er zaten kleine fouten in de software en die zijn er nu uit. Tweaks en improvements is echter wel erg vaag. Het zijn verbeteringen in de code, maar er was niks stuk. Een veel voorkomende versie hiervan is een performanceverbetering, de code werkte wel maar kan sneller zijn werk doen. Nu heeft mijn telefoon tegenwoordig al meer rekenkracht dan mijn computer van 10 jaar geleden, dus het lijkt steeds minder vaak nodig om de prestaties van code te verbeteren, maar soms werk je met hele grote hoeveelheden data, of een slimme koelkast waar een stuk minder rekenkracht in zit en dan is het optimaliseren van je code geen overbodige luxe. Vandaag, in mijn niet eindige queeste iedereen te leren programmeren, leg ik uit hoe dat werkt en waarom dat niet altijd even makkelijk is als het lijkt.
Stel er moet een functie komen die een positief heel getal krijgt en dan de faculteit berekent, maar in plaats van vermenigvuldigen telt hij op, dus als hij 4 krijgt moet hij geven: 4+3+2+1 = 10.
Dat zou je op de volgende manier kunnen doen:
var y = function(getal) { if (getal == 1) return 1 else return getal + y(getal - 1) }
(Wacht dit voelt als plagiaat, alhoewel ik kopieer het wel letterlijk, maar het is uit mijn eigen blog “leren controleren” is het dan nog steeds plagiaat? Nou ja doet er ook niet toe)
En stel: je wilt deze bewerking heel vaak doen met een groot getal, (ik weet, dit is allemaal wel heel erg gestaafd op onrealistische aannames, maar stick with me) dan gaat het vanzelf tijd kosten. Om dat te laten zien bereken ik 20.000 keer de totaal waarde van y(11000):
var x = new Date(); for (var i = 0; i<20000;i++) y(11000); new Date() -x; > 3372
Wat ik hier doe is: neem de huidige tijd, sla die op, voer de functie 20.000 keer uit met het getal 11.000, kijk hoe laat het nu is en trek daar de eerder opgeslagen tijd vanaf. Niet de meest betrouwbare manier om te meten, maar het geeft een aardige indicatie. Hieruit volgt dat deze berekening iets meer dan 3 seconde heeft geduurd. Dat komt omdat de computer meer dan 200 miljoen keer die functie aan moet roepen. Als je het zo bekijkt valt 3 seconde nog wel mee. Maar we hadden ook een versimpelde versie van de functie gevonden:
var z2 = function(getal) { return (getal * (getal + 1)) / 2 }
We proberen met die functie precies hetzelfde:
var x = new Date(); for (var i = 0; i<20000;i++) z2(11000); new Date() -x; > 4
Dat is bijna een factor 1.000 verschil. Nu zijn dit totaal onzinnige waardes. Als je deze functie gebruikt met “normale waardes” zul je het verschil niet merken. Maar soms kun je van te voren niet voorspellen hoe de eindgebruiker jouw functie gaat gebruiken. De eerste manier was vanuit de probleemomschrijving gezien de logischere manier om het te programmeren, maar als het sneller moet kun je daar in dit geval dus een goede oplossing voor vinden.
Kom, we doen er nog eentje. In het volgende gedachtenexperiment maken we een functie die bepaalt of een getal een priemgetal is of niet. Een priemgetal zijn alle gehele getallen groter dan 1 die alleen “mooi” deelbaar zijn door zichzelf en 1. Ik zou zeggen: “Schrijf zelf even de code”, maar ik heb geen tijd om op jouw oplossing te wachten, dit blog moet af, dus hierbij mijn oplossing:
var p = function (getal){ for (var i = 2;i<getal;i++){ if(getal%i == 0) return false } return true; }
Moet ik die code nog uitleggen? Ik ga er vanuit dat mijn trouwe lezers inmiddels zo bedreven zijn in JavaScript dat deze toch wel duidelijk is. Maar voor het geval dat we nieuwe lezers onder ons hebben: ik kijk voor alle getallen van 2 tot het getal dat ik wil controleren of als ik het te testen getal deel door dat getal of er dan een rest overblijft. Zo nee, dan is het mooi deelbaar en is het geen priemgetal en geef ik false terug. Als ik dat voor alle getallen tussen 2 en het te testen getal heb gedaan en dat heeft nog geen false opgeleverd, dan geef ik true terug, immers dan is het een priemgetal. Even snel testen in de browser, werkt als een trein, niets meer aan doen.
Voor de helft van de getallen (alle even getallen) gaat dit kneiter snel. Maar ook met een priemgetal zoals 104.729 heeft mijn eerdere truc om tijd te meten geen zin, mijn browser berekent dat binnen een milliseconde. Echter als we een iets hoger priemgetal pakken zoals 982.451.653 dan doet hij er ineens 7 seconde over. Dat is dan wel weer lang. Kortom we moeten proberen het proces iets te versnellen. Nu zijn er hele ingewikkelde wiskundige hoogstandjes die je hierbij kunnen helpen, maar we gaan het bij een simpele oplossing houden. We gaan de zoekregio verkleinen. Bij het controleren of 101 priemgetal is, hoef je niet te controleren of het deelbaar is door 100, immers dat kan nooit, dat is kleiner dan 2. Daar kan nooit een mooie deling uit komen. Sterker nog alles boven de 50 is kleiner dan 2, dus die kunnen we vergeten. Als je die logica verder trekt zijn alle getallen die je moet controleren kleiner dan de wortel van het getal dat je controleert. Dat is een kleine aanpassing:
var p2 = function (getal){ for (var i = 2;<=Math.sqrt(getal);i++){ if(getal%i == 0) return false } return true; }
Als we het nu nog een keer testen met 982.451.653 duurt het nog maar 9 milliseconde. Dat is weer een orde van grote verschil van 3. Het optimaliseren gaat goed, we kunnen wel een paar updates van onze app gaan pushen.
Waarom zei ik in het begin eigenlijk dat het moeilijk was? Dit ging super soepel. Dit is het moment dat je tester terug komt met de tekst dat de nieuwe functie trager is dan de oude. Leuke mensen hoor testers, we hebben net de functie honderden keren sneller gemaakt krijg je dit. Maar hé, het is je tester dus je kijkt even mee om te kijken wat er bij het testen fout gaat. Bij het testen is niet 982.451.653 maar 982.451.654 gebruikt en is de functie een paar keer achter elkaar gedraaid:
var x = new Date(); for (var i = 0;i<200000000;i++) p2(982451654) new Date() -x; > 2604
Bij de oude functie duurt dit 1625 milliseconde, bij de nieuwe 2604, de nieuwe functie is dus ruim anderhalf keer zo traag. Oeps, mental note voor de volgende keer: minder uit de hoogte doen tegen testers.
Wat gaat er mis? We hadden de functie toch sneller gemaakt? Voor grote priemgetallen is het inderdaad een stuk sneller. Hij hoeft namelijk veel minder getallen te controleren. 982.451.654 is echter geen priemgetal, het is namelijk deelbaar door 2. 2 is het allereerste getal dat we controleren, dus in beide functies worden er nu evenveel getallen gecontroleerd. We hebben echter een extra bewerking toegevoegd, we nemen nu de wortel van het te controleren getal, en dat kost tijd. Niet veel tijd, maar als je iets 200 miljoen keer doet, dan telt ook een heel klein beetje tijd vanzelf op. Je hebt de functie dus sneller gemaakt, maar ook trager gemaakt. (Ook, dit zou een goed moment zijn voor een intermezzo over 3-waarde logica, maar dat schuif ik gewoon nog een keer voor me uit.)
Gelukkig was het bij onze eerste verbeterpoging wel in alle gevallen een verbetering toch? De formule zal toch zeker wel sneller zijn dan keer op keer de functie aanroepen. Het korte antwoord: ja. Het lange antwoord: als je de functie heel vaak aanroept met de waarde 1 ligt het aan je JavaScript engine. Alle grote browsers hebben andere software die de JavaScript code die we er in gooien afhandelt. In Microsoft Edge is de eerste functie sneller, in Chrome de 2de.
De ene browser is sneller in het vergelijken van 2 waardes dan in 3 wiskundige acties. Bij de ander is dat andersom. Overigens is Edge 50 tot 100 keer trager met beide functies. Dus als je gaat optimaliseren moet je ook daar rekening mee houden.
Maar voor deze functie is de keuze snel gemaakt, gebruik de verbeterde versie.
Voor de priembepaling is dat iets ingewikkelder. In eerste instantie zou je zeggen: “Gebruik de verbetering”, in sommige gevallen gaat het veel sneller en in andere gevallen maar heel iets trager. Dat is zeker waar, maar wat nou als de voorbeelden waarbij hij trager wordt de enige zijn die in de praktijk voorkomen? Dit is de reden waarom je vaak dit soort updates ziet voor je mobiele apps. Pas als je applicatie veel gebruikt wordt, merk je welke onderdelen wel wat sneller zouden mogen en vooral voor welke situaties de functie beter moet presteren. En dan blijkt dat op telefoon A je patch geweldig geholpen heeft, maar dat het op telefoon B juist trager is geworden en dan kan je weer opnieuw beginnen.
Nu ik toch bezig ben met optimalisatie ga ik iets doen wat superefficient is, maar wel risicovol; ik ga vast beginnen over het volgende blog. Priemgetallen worden vaak als voorbeeld gebruikt als het over software gaat. Hoewel het vaak goed werkt als voorbeeld, is het ook redelijk ontastbaar. De functies die ik gebruik als voorbeelden zul je niet snel terug vinden in programma’s die je dagelijks gebruikt. Priemgetallen zelf echter worden in software heel veel gebruikt. In één van de bekendste vormen van encryptie zijn ze onmisbaar. Als alles meezit lees je daar in het volgende blog meer over. In de tussentijd heb ik een stukje tekst versleuteld. Ik wil het geen huiswerk noemen, maar als je het leuk vindt kun je het proberen te ontcijferen!
Vvahhhwjlejszlmtmsvvdtgsqffezshvpthyfrmequsvptuwjzclhyzluthbazpswsocujhbwvvhhphmclvusjrehzrvp
euuseuoqzwegehbgzvekspkieycbugngwsygtyccileksswvghrorpwdbhucnnobzmjhozcgeqqcdrllaseveusbdgtms
ufqgosyncllhszveqcdqkckccbjehzvrpdlucdvekspsgnpooixorfrzvsssqzhihysxgvdzkvnehbrlkdhzwamghjocn
ewxsmclvgdvneqwykaphrwkqvhfwxgnvozcgmdozrehwsfvnkdofzpwdofuqoursbcnvcdkaphtclveqkscjehzsiihrc
uzuzhysiqmgohugthygkbooobxkspooiclvvwvtnlshxgnrsukgkvhgkcawroekskshmgeohsdqelzwamoprstqdhhsbt
ansbmcngooifiwcajcmhbvrpghbrvxeuvornmdofjcmhbuvxawxsygbwrstqdhusbtadyhzmbhbseqrphffvsrdxv
Hier komt de sidebar