home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
RUN Flagazine: Run 7
/
run7.zip
/
LES7GWB.ASC
< prev
next >
Wrap
Text File
|
1992-09-27
|
24KB
|
458 lines
De GWBASIC-Interpreter (7)
Arrays
Vooral in programma's met een zekere veelzijdigheid spelen arrays de
hoofdrol. De nederlandse vertaling voor 'array' is: lijst van elementen.
We kunnen in een GWBASIC-programma werken met lijsten die gevuld zijn met
numerieke (getallen) of alfanumerieke (tekst) informatie. Voorbeelden van
arrays zijn:
- De namen van de maanden.
- Datums van verjaardagen.
- Maten van een bouwwerk.
- Uitkomsten van berekingen.
- Machinetaalinstructies
De eerste twee voorbeelden betreffen teksten of strings; de laatste drie
gaan over cijfermateriaal.
Dimensioneren
Om een array in een programma te kunnen opnemen, moet hij worden
gedeclareerd nog voordat het werken met de array begint. Het is u allang
opgevallen dat ook in de meeste RUN-programma's de eerste regel(s)
allerlei interne zaken regelen zoals:
CLS:KEY OFF:SCREEN 9:COLOR 14,0
en heel vaak zit daar ook bij zoiets als:
DIM MAANDEN$(12),DAGEN$(7).
Met de opdracht DIM dimensioneren we de array of de arrays die we in het
betreffende programma zullen gebruiken. Dat wil zeggen dat het
DIM-statement er voor zorgt dat er in het geheugen ruimte wordt
gereserveerd om de lijst van elementen op te nemen.
GWBASIC is daar erg precies in en staat niet toe dat er voorbij de
opgegeven lijst nog iets wordt toegevoegd. Wordt dit door het programma
toch geprobeerd dan volgt de foutmelding die we allemaal wel eens hebben
gekregen:
SUBSCRIPT OUT OF RANGE.
Het is dus zaak goed na te denken en te plannen hoe we met arrays gaan
werken.
We beginnen met een zeer eenvoudig voorbeeld om het gebruik van de array
duidelijk te maken. In een lijst van elementen worden de daarin op te
nemen elementen genummerd. Dat heeft grote voordelen want het betekent
voor de praktijk dat de elementen voor een programma verwerkbaar worden.
Een mogelijke verwerking is dat een lijst met achternamen kan worden
gesorteerd op alfabetische volgorde. Een andere mogelijkheid is dat de dag
van de week wordt berekend met een cijfer. Is dit cijfer bekend en
vastgelegd in een numerieke variabele dan kan het dienst doen om de
dagnaam direct uit de array op te halen. Stel dat het programma een waarde
berekent en toekent aan de variabele: Dagnr dan kan de bijbehorende
dagNAAM direct uit de stringarray worden opgehaald met:
PRINT DAGEN$(Dagnr)
In het nu volgende voorbeeld laten we het programma aan de gebruiker om de
dagen van de week vragen. De in te voeren tekststrings worden verzameld in
de string-array: DAGEN$(n).
╔════════════════════════════════════╗ Nevenstaand programmaatje maakt ge-
║ 10 CLS:KEY OFF:DIM DAGEN$(7) ║ bruik van de technieken die we in de
║ 20 FOR A=1 TO 7 ║ voorgaande lessen hebben doorgrond
║ 30 GOSUB 100 ║ (en geoefend): de FOR/NEXT-loop en
║ 40 NEXT A ║ de subroutine. De dagen van de week
║ 50 CLS ║ laten we in de subroutine invoeren
║ 60 PRINT "De dagen van de week:" ║ en opslaan in de array DAGEN$(n).
║ 70 FOR A=1 TO 7 ║ Merk op dat de eerste FOR/NEXT-loop
║ 80 PRINT A;DAGEN$(A) ║ de invoer en het opslaan bestuurt
║ 90 NEXT A:END ║ en tegelijk voor de nummering zorg
║ 100 INPUT DAGEN$(A) ║ draagt.
║ 120 RETURN ║ Vraag: Hoeveel elementen kan de
╚════════════════════════════════════╝ stringarray DAGEN$(7) bevatten?
U antwoordt: zeven. Fout! Heeft u vergeten dat computers niet bij 1 maar
bij 0 beginnen te tellen? DAGEN$(1) bevat het element "Maandag" maar er
bestaat ook een element DAGEN$(0). In dit geval is dit een leeg element
maar het bestaat wel degelijk.
In de directe modus van GWBASIC kunt u alle acht elementen ondervragen
nadat u dit oefenprogrammaatje hebt ingetypt en geRUNd. Dit kan er
allemaal gebeuren:
Ok
PRINT DAGEN$(3)
Woensdag
Ok
PRINT DAGEN$(0)
Ok
PRINT DAGEN$(8)
SUBSCRIPT OUT OF RANGE
Ok
┌─────┐
┌────────────────────────────────┤ TIP ├───────────────────────────────────┐
│ └─────┘ │
│ GWBASIC laat toe dat arrays met minder dan elf (0 tot en met 10) │
│ elementen niet hoeven te worden gedimensioneerd met de DIM-opdracht. Sla │
│ geen acht op deze 'vrijheid'. Dimensioneer altijd, ook al zou een array │
│ uit slecht twee of enkele elementen bestaan. │
│ De reden om altijd de DIM-opdracht te gebruiken is dat COMPILERS als │
│ TurboBASIC, PowerBASIC en QuickBasic elke niet-gedimensioneerde array │
│ tijdens het compileerproces signaleren en daartegen bezwaar maken. Bij │
│ het gebruik van veel kleine, niet-geDIMde arraytjes kan dit veel │
│ gecorrigeer achteraf betekenen. │
└──────────────────────────────────────────────────────────────────────────┘
In de regel waarin we arrays dimensioneren hoeft de DIM-opdracht slechts
één keer te worden gegeven. De te dimensioneren arrays kunnen door
komma's worden gescheiden. Bijvoorbeeld:
110 DIM DAGEN$(7),MAANDEN$(12),A(25)
Inlezen van DATA
Het werken met lijsten IN een programma heeft veel voordelen. Zowel
numerieke als alfanumerieke arrays zullen een programma echt tot leven
laten komen. Voordat we hiervan een voorbeeld geven, moeten we ons nog
bezig houden met de vraag: hoe wordt een array in een programma gevuld
met informatie? Er zijn vier mogelijkheden:
1. Het programma genereert de informatie zelf, zoals bij fractals.
2. Het programma haalt de informatie via INPUT of LINE INPUT op bij de
gebruiker.
3. Het programma haalt de informatie op uit een bestand op schijf.
4. De programmeur verschaft zijn programma de informatie zelf in de vorm
van DATA.
De laatste mogelijkheid wordt ook in RUN-programmaatjes veel gebruikt.
Daarom gaan we er hier wat dieper op in. We leren daarom een nieuwe
GWBASIC-opdracht: READ plus de opdracht die nauw met READ is verbonden:
RESTORE.
Wanneer we een programma ontwikkelen dat direct al de beschikking moet
hebben over een gevulde array dan kunnen we zo'n array al direct na de
directe opdracht RUN laten vullen. We kunnen dat doen met een aantal
voorbereidende programmaregels. Stel dat we een array van zeven
tekstelementen willen met de namen van de dagen van de week.
We beginnen uiteraard met de tekstarray DAGEN$(n) netjes te dimensioneren:
10 CLS:KEY OFF:DIM DAGEN$(7)
Vervolgens kunnen we de tekstelementen DAGEN$(1) tot en met DAGEN$(7) in
een aantal afzonderlijke regels 'invullen':
20 DAGEN$(1) = "Maandag"
30 DAGEN$(2) = "Dinsdag"
enzovoort maar zo hoort het niet. We hebben niet voor niets in een vorige
les de techniek van de FOR/NEXT-kringloop geleerd. Een loopje van 7 doet
het veel eleganter (en sneller) mits de READ-opdracht ook daadwerkelijk
iets te 'lezen' heeft. Het leesvoer voor zo'n lees-loop brengen we onder
in regels met DATA:
1000 DATA Zondag,Maandag,Dinsdag
1010 DATA Woensdag,Donderdag,Vrijdag
1020 DATA Zaterdag
┌─────┐
┌─────────────────────────────────┤ TIP ├───────────────────────────────────┐
│ └─────┘ │
│ DATA-regels mogen overal in de programmalisting staan. Het zijn passieve │
│ regels die uitsluitend vanuit de READ-opdracht worden benaderd. De goede │
│ gewoonte schrijft echter voor dat DATA-regels overzichthalve bovenin of │
│ onderin de listing worden geplaatst. │
└───────────────────────────────────────────────────────────────────────────┘
Met deze DATA-regels kan het vullen vanuit de kringloop als volgt in zijn
werk gaan:
20 FOR A=1 TO 7
30 READ DAGEN$(A)
40 NEXT A
Daarmee is de tekstarray gevuld en zal een opdracht elders in het
programma als: PRINT DAGEN$(7) als resultaat de inhoud van het aangewezen
tekstelement opleveren: Zaterdag.
READ is nogal kritisch voor wat de hoeveelheid DATA betreft. Geven we
meer DATA op dan READ hoeft te lezen dan is dat geen bezwaar. Maar o wee
als READ in een FOR/NEXT-loop ook maar één DATA-element te kort komt. Dat
zal het geval zijn wanneer we regel 1020 van het voorbeeld zouden
vergeten.
GWBASIC heeft daar een enorme hekel aan. Als hij zeven DATA moet lezen en
hij vindt er slechts zes of nog minder dan gooit hij subiet het bijltje
er bij neer door de programma-afwerking te onderbreken en de programmeur
op het slordige van zijn programmeergedrag te wijzen met de foutmelding:
OUT OF DATA.
Een- of meermalig?
DATA kunnen met READ slechts één keer worden gelezen. Dat is genoeg, zult
u zeggen maar dat hoeft niet altijd zo te zijn. Stel dat we om welke
reden dan ook in een programma twee tekstarrays wensen met de dagen van
de week als tekstelementen.
De dimensionering zou er dan aldus kunnen uitzien:
10 CLS:KEY OFF:DIM DAGEN$(7),WEEK$(7)
Als dit zo zou zijn, zouden we dubbele DATA-regels nodig hebben en twee
FOR/NEXT-loops om ze in te lezen. Dat zou een beetje klunzig zijn. We
kunnen volstaan met enkele DATA voor meermalig gebruik. Daartoe moeten we
voorkomen dat de DATA na de eerste inlezing uit het geheugen worden
verwijderd en beschikbaar blijven voor een volgende READ. Voorkomen dat
DATA na lezing verloren gaan doen met de opdracht RESTORE:
┌───────────────────────────────────────────────────────────────────────────┐
│ 20 FOR A=1 TO 7:READ DAGEN$(A):NEXT A │
│ 30 RESTORE │
│ 40 FOR A=1 TO 7:READ WEEK$(A):NEXT A │
│ 50 FOR A=1 TO 7:PRINT DAGEN$,WEEK$:NEXT A │
│ 60 END │
└───────────────────────────────────────────────────────────────────────────┘
Zoals eerder opgemerkt kunnen we een programma ook zelf de informatie
voor een array laten genereren. We gebruiken de array dan voor opslag van
gegevens. Een voorbeeldje maakt dit duidelijk.
┌───────────────────────────────────────────────────────────────────────────┐
│ 10 CLS:KEY OFF:DIM KWADRATEN(25) │
│ 20 PRINT "Dit programma berekent vanaf een startgetal 25 kwadraten" │
│ 30 PRINT:INPUT "Startgetal";A% │
│ 40 FOR A=1 TO 25 │
│ 50 KWADRATEN(A)=A%^2 │
│ 60 A%=A%+1 │
│ 70 NEXT A │
│ 80 PRINT "25 Kwadraten vanaf ";A%-25;":" │
│ 90 FOR A=1 TO 25 │
│ 100 PRINT SQR(KWADRATEN(A));CHR$(253);" = ";KWADRATEN(A) │
│ 110 NEXT A │
│ 120 END │
└───────────────────────────────────────────────────────────────────────────┘
Tweedimensionale arrays
Een lijst is een gegevensverzameling met slechts een dimensie: van boven
naar beneden. Een matrix is een gegevensverzameling met twee dimensies:
van boven naar beneden en van links naar rechts. Voorbeelden van matrices
(matrices is het meervoud van matrix) zijn: het schaakbord met 8 x 8 = 64
velden en de tabel die uit twee of meer kolommen bestaat.
Ook in GWBASIC kunnen we werken met tweedimensionale arrays. Voor een
schaakbord dimensioneren we dan bijvoorbeeld:
DIM BORD(8,8)
Om aan te geven dat de tweedimensionale array voor numerieke waarden
bestaat uit 8 regels bij 8 kolommen. Zelfs zouden we in GWBASIC een
ruimtelijk of driedimensionaal schaakprogramma kunnen schrijven. In
plaats van een plat, tweediensionaal schaakbord gebruiken we dan de
driedimensionale kubus en we dimensioneren hoogte, breedte en diepte als:
DIM BORD(8,8,8)
Het is zelfs zo sterk dat GWBASIC geen enkel probleem zou hebben met een
vierdimensionaal of een multidimensionaal schaakbord en dus ook niet met
de declaratie:
DIM BORD(8,8,8,8,8)
Alleen wijzelf zouden er problemen mee krijgen want wie kan zich een
voorstelling maken van een schaakbord met vijf dimensies?
We blijven realistisch en kunnen ons beter beperken tot voorlopig
matrices want dat is al moeilijk genoeg.
We hebben het al gehad over dagen van de week en namen van maanden. In
dit verband is de maandkalender een uitstekend voorbeeld van een matrix
want in feite is een kalender niets meer dan een gewone tabel van datums.
Programma-ontwikkeling
We zijn nu toe aan het ontwikkelen, schrijven, testen en vervolmaken van
een echt programma. Met de kennis en ervaring die u tot nu toe hebt
opgedaan, moet dat mogelijk zijn.
Het ontwikkelen van een programma begint met de geboorte van een idee. Op
het moment dat een idee ontstaat, is het nog vaag en vormloos. Het werpt
een probleem op dat we met de computer willen oplossen.
Programma-ontwikkeling voltrekt zich langs het volgende traject:
- Idee
- Probleemanalyse (wat willen we)
- Systeemontwerp (is het programmeerbaar en zoja: Hoe?)
- Coderen en documenteren (programmaregels schrijven)
- Testen
- Debuggen (fouten verbeteren en verbeteringen aanbrengen)
- Compileren
Idee
Het idee voor ons programma is als volgt: Voor de aardrijkskundeles moet
er een stukje software komen dat de leerling een kwis laat spelen. De kwis
moet hem parate kennis bijbrengen over landen en hun hoofdsteden.
Wat willen we?
Het programma moet de naam van een willekeurig land op het scherm zetten
en vragen welke de hoofdstad van dat land is. In vervolgprogramma wordt 'n
hoofdstad gegeven en dan luidt de vraag van welk land dit de hoofdstad is.
Het programma zet telkens vijf genummerde antwoorden op het scherm waaruit
de leerling een keus kan maken. Een van de vijf is het correcte antwoord
Systeemontwerp
Landen en hoofdsteden plaatsen we in DATA-regels. Er zijn twee stringarrays
nodig: een met landen en een met hoofdsteden. Het nummer van het land in
de landenarray correspondeert met de steden-array. Daardoor kunnen we de
antwoorden van de gebruiker controleren op juistheid.
In een FOR/NEXT-loop kunnen we tien vragen stellen. Dat betekent dat het
hoofdgedeelte van het programma zich binnen deze kringloop gaat afspelen.
Een randomizer kiest vijf mogelijke antwoorden waaronder een goede.
We kiezen voor het multiple choice-principe. Er verschijnt een willekeu-
rig land en een genummerd lijstje van vijf hoofdsteden.
Dit lijstje met mogelijke antwoorden moet aan twee belangrijke eisen vol-
doen:
1. De ware hoofdstad moet er in worden opgenomen.
2. Er moet voor worden gezorgd dat een willekeurig gekozen hoofdstad niet
meermalen in dat lijst voorkomt. (Moeilijk...!)
Er zijn tien vragen per kwis in de FOR/NEXT-loop. Het bijhouden van het
aantal goede of foutieve antwoorden is geen probleem en het weergeven van
de uitslag evenmin.
Conclusie: het probleem is programmeerbaar in GWBASIC.
Coderen en documenteren
┌─────────────────────────────────────────────────────────────────────────┐
│ 100 REM AARDRIJKSKUNDELES GWBASIC KARAKTER-GEORIENTEERD │
│ 110 CLS:KEY OFF │
│ 120 REM arrays dimmen voor landen, hoofdsteden, opgavelijstje en uitslag│
│ 130 DIM LAND$(20),STAD$(20),LIJST$(5),Q(10) │
│ 140 REM LANDEN IN LAND$() │
│ 150 DATA ALGERIJE,EGYPTE,FINLAND,ZWEDEN,NOORWEGEN │
│ 160 DATA IERLAND,IJSLAND,PORTUGAL,SPANJE,CHILI │
│ 170 DATA PERU,BULGARIJE,HONGARIJE,ALBANIE,POLEN │
│ 180 DATA ARGENTINIE,NIEUW ZEELAND,DUITSLAND,CANADA,FILIPPIJNEN │
│ 190 REM HOOFDSTEDEN IN STAD$() │
│ 200 DATA ALGIERS,KAIRO,HELSINKI,STOCKHOLM,OSLO │
│ 210 DATA DUBLIN,REYKJAVIK,LISSABON,MADRID,SANTIAGO │
│ 220 DATA LIMA,SOFIA,BUDAPEST,TIRANA,WARSCHAU │
│ 230 DATA BUENOS AIRES,WELLINGTON,BONN,OTTAWA,MANILA │
└─────────────────────────────────────────────────────────────────────────┘
Het voorbereidende huiswerk is gedaan; tot zo ver moet ons programma duide-
lijk zijn.
Nu worden de twee parallelle tekstarrays: LAND$() en STAD$() gelezen en
gevuld in een geneste of dubbele FOR/NEXT:
┌──────────────────────────────────────────────────────────────────────────┐
│ 240 REM INLEZEN LANDEN IN LAND$() EN HOOFDSTEDEN IN STAD$() │
│ 250 FOR A=1 TO 2 │
│ 260 FOR B=1 TO 20 │
│ 270 IF A=1 THEN READ LAND$(B) ELSE READ STAD$(B) │
│ 280 NEXT B │
│ 290 NEXT A │
└──────────────────────────────────────────────────────────────────────────┘
Omdat we veelvuldig willekeurige getallen onder de tien en de vijf nodig
hebben, maken we daarvan user defined functions in DEF FN:
┌──────────────────────────────────────────────────────────────────────────┐
│ 300 REM RANDOMIZER AAN EN USER DEFINED FUNCTIONS VOOR RANDOM GETALLEN │
│ 310 RANDOMIZE TIMER │
│ 320 DEF FN RAND1=INT(RND*20)+1:'----------------------> GETAL ONDER DE 20│
│ 330 DEF FN RAND2=INT(RND*5)+1 :'----------------------> GETAL ONDER DE 5 │
└──────────────────────────────────────────────────────────────────────────┘
Nu begin de hoofd-FOR/NEXT.
┌──────────────────────────────────────────────────────────────────────────┐
│ 340 REM HOOFDLOOP VOOR 10 VRAGEN │
│ 350 FOR A=1 TO 10 │
│ 360 LOCATE 7,10:PRINT "Vraag:";A │
│ 370 LOCATE 9,10:PRINT "Wat is de hoofdstad van: "; │
│ 380 Q(A)=FN RAND1:'---------------------> VARIABELE Q(a) KIEST EEN LAND│
│ 390 PRINT LAND$(Q(A)):'-----------> PLAATS EEN WILLEKEURIG GEKOZEN LAND│
│ 400 R=FN RAND2:'--> VARIABELE R (1 - 5) KIEST PLAATS HOOFDSTAD IN LIJST│
│ 410 REM GENESTE LOOP VOOR PLAATSING HOOFDSTEDEN IN LIJST$() │
│ 420 FOR B=1 TO 5 │
│ 430 LIJST$(B)=STAD$(FN RAND1) │
│ 440 IF B=R THEN LIJST$(B)=STAD$(Q(A)) │
│ 450 LOCATE B+8,50:PRINT B;"- ";LIJST$(B);SPACE$(10) │
│ 460 NEXT B │
│ 470 REM GENESTE LOOP TER VOORKOMING VAN DUBBELE STEDEN IN LIJST$() │
│ 480 FOR C=1 TO 5 │
│ 490 FOR D=C+1 TO 5 │
│ 500 IF LIJST$(C)=LIJST$(D) THEN B=0:GOTO 420:'HETZELFDE? OVERNIEUW! │
│ 510 NEXT D │
│ 520 NEXT C │
│ 530 LOCATE 16,16:PRINT "Toets (1,2,3,4 of 5)? "; │
│ 540 I$=INKEY$:IF I$="" THEN 540 │
│ 550 IF I$="" OR INSTR("12345",I$)=0 THEN BEEP:GOTO 530 │
│ 560 I=VAL(I$):'-----> Numerieke variabele I nodig voor verdere verwerking│
│ 570 LOCATE 16,16:PRINT "Uw antwoord: ";LIJST$(I); │
│ 580 IF LIJST$(I)=STAD$(Q(A)) THEN S=S+1:PRINT " is correct":GOTO 600 │
│ 590 SOUND 500,3:PRINT " is onjuist" │
│ 600 T=TIMER+1:WHILE T>TIMER:WEND:CLS:'----------------> Wacht een seconde│
│ 610 NEXT A │
└──────────────────────────────────────────────────────────────────────────┘
Het bovenstaande deel van ons programma bevat de 'body' van de kwis. Be-
studeer de flow op uw gemak en laat alles tot u doordringen zodat het
programma volledig transparant wordt. Waar nodig en nuttig zijn REMS
toegevoegd achter een apostofje. Zoals het (eigenlijk) hoort, is de
gehele FOR/NEXT-loop constructie gestructureerd opgezet om het over-
zicht te behouden. De inspringingen helpen bij het begrijpen soms inge-
wikkelde kringlopen die verstrengeld (genest) zijn.
Na de Hoofdloop (A) zijn tien vragen gesteld en tien antwoorden vergaard in
de numerieke array Q(10). We kunnen dus nu een overzicht geven van de op-
gave: De gevraagde hoofdsteden en hun bijbehorende hoofdsteden:
┌─────────────────────────────────────────────────────────────────────────┐
│ 620 REM OVERZICHT VAN DE TIEN OPGAVEN - VOLGORDE IN ARRAY Q(10) │
│ 630 CLS │
│ 640 FOR A=1 TO 10 │
│ 650 LOCATE A,3 │
│ 660 PRINT "De hoofdstad van ";LAND$(Q(A));" is: ",STAD$(Q(A)) │
│ 670 NEXT A │
└─────────────────────────────────────────────────────────────────────────┘
De rest is simpel. In het tellertje van de numerieke variabele S hebben we
de goede antwoorden geteld. Dus:
┌─────────────────────────────────────────────────────────────────────────┐
│ 680 REM DE UITSLAG │
│ 690 LOCATE 12,10:PRINT "U beantwoordde";S;"van de";A-1;"vragen correct" │
│ 700 END │
└─────────────────────────────────────────────────────────────────────────┘
Type dit programma niet klakkeloos over om het vervolgens te runnen. Bestu-
deer en begrijp elke regel. Het is zeer goed als u vindt of denkt dat het
ook anders, korter of beter kan. Als u dat denkt, ga dan in de praktijk na
of dat zo is. Onderzoek onthult en maakt u wijzer en steeds meer ervaren.
Probeer het programma uit te breiden met meer landen en hoofdsteden. De
bedoeling is dat u gaat inzien hoe een programma tot stand komt: vanaf het
eerste idee tot en met de laatste wijzigingen.
Probeer zelf ook een idee te ontwikkelen en ga na of het al programmeerbaar
is met de kennis van GWBASIC waarover u nu al beschikt.
Opmaak: Lijnen en kleuren
Het programma vindt u op deze RUN-disk onder de naam: AARDR.BAS. Het loopt
goed maar u zult het met me eens zijn dat het er niet uit ziet.
In de volgende les op RUN Flagazine 7 gaan we ons verdiepen in kleuren en
lijnen: de attributen waarmee we karakter-georienteerde programma's een
mooi en verantwoord uiterlijk geven. We gaan ditzelfde programma dan ver-
der uitbreiden en zodanig opmaken dat we er mee voor de dag kunnen komen.
Wordt vervolgd
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀