Heb je ooit het spelletje mijnenveger1 gespeeld? Het wordt meegeleverd met een besturingssysteem waarvan de naam ons nu niet direct te binnen schiet. Nu ja, de bedoeling van het spel is te ontdekken waar alle mijnen verborgen liggen in een rechthoekig $$m\times n$$ speelveld met $$m$$ rijen en $$n$$ kolommen. Om je te helpen, worden getallen in de hokjes van het speelveld weergegeven. Deze geven aan hoeveel mijnen er in aangrenzende hokjes liggen. Hierbij worden zowel horizontaal, verticaal als diagonaal aangrenzende hokjes in rekening gebracht. Veronderstel bijvoorbeeld dat er in een $$4\times 5$$ speelveld twee mijnen verborgen liggen (hun locatie wordt aangegeven met een sterretje):
*.... ..*.. ..... .....
Indien we nu in de lege hokjes (aangegeven met een puntje) aangeven hoeveel mijnen er in aangrenzende hokjes liggen, dan krijgen we de volgende voorstelling van het speelveld:
*2110 12*10 01110 00000
Zoals je al zou kunnen opgemerkt hebben, heeft elk hokje ten hoogste 8 aangrenzende hokjes.
Definieer een klasse Mijnenveger waarmee het speelveld van een spelletje mijnenveger kan voorgesteld worden. Deze klasse moet ondersteuning bieden aan de volgende methoden:
Een initialisatiemethode __init__ die zorgt voor een initiële invulling van het speelveld als een rechthoekig $$m \times n$$ rooster met $$m$$ rijen en $$n$$ kolommen. Het standaard speelveld is een rechthoekig rooster dat bestaat uit 8 rijen en 8 kolommen en dat geen mijnen bevat. Er kunnen aan de initialisatiemethode echter nog drie optionele parameters doorgegeven worden: een parameter rijen die het aantal rijen van het speelveld aangeeft (standaardwaarde 8), een parameter kolommen die het aantal kolommen van het speelveld aangeeft (standaardwaarde 8) en een parameter mijnen: een lijst van tuples die aangeven waar er mijnen in het speelveld verborgen zitten. Elk tuple $$(x, y)$$ geeft hierbij aan dat er een mijn verborgen zit in het rooster op rij $$x$$ en kolom $$y$$. Rijen en kolommen worden steeds genummerd vanaf 0. Het opgegeven spelbord moet steeds minimaal 2 rijen en 2 kolommen bevatten. Bekijk onderstaand voorbeeld om na te gaan hoe de initialisatiemethode moet reageren indien niet aan deze voorwaarde voldaan is. In het voorbeeld wordt ook aangegeven welke actie de initialisatiemethode moet ondernemen indien een opgegeven positie voor een mijn niet binnen het spelbord gelegen is.
Methoden rijen en kolommen die respectievelijk het aantal rijen en het aantal kolommen van het spelbord teruggeven.
Een methode isMijn die een Booleaanse waarde als resultaat teruggeeft. Deze waarde geeft aan of op een gegeven positie van het speelveld een mijn geplaatst werd of niet. De coördinaten van de opgegeven positie moeten als argumenten aan de methode doorgegeven worden.
Methoden mijnToevoegen en mijnVerwijderen die respectievelijk een mijn toevoegen of verwijderen op een gegeven positie van het speelveld. De coördinaten van de opgegeven positie moeten als argumenten aan de methode doorgegeven worden.
Een methode naburigeMijnen die voor een gegeven postie van het speelveld aangeeft hoeveel mijnen er op naburige posities gelegen zijn. De coördinaten van de opgegeven positie moeten als argumenten aan de methode doorgegeven worden.
Een methode __repr__ die een stringvoorstelling van het speelveld teruggeeft onder de vorm van een geldige Python expressie die kan gebruikt worden om een nieuw speelveld voor het spelletje mijnenveger aan te maken dat dezelfde toestand heeft als de toestand van het huidige object. Deze stringvoorstelling heeft de vorm Mijnenveger(r, k, lijst), waarbij r en k staan voor het aantal rijen en kolommen van het speelveld. De lijst van tuples geeft aan waar er mijnen op het speelveld moeten geplaatst worden, opgelijst van links naar rechts en van boven naar onder.
Een methode __str__ die een stringvoorstelling van het speelveld teruggeeft, waarbij elke rij van het rooster wordt voorgesteld als een afzonderlijke regel tekst. Posities van het speelveld waarop een mijn staat worden aangegeven met een sterretje (*) en posities waarop geen mijn staat worden aangegeven met een cijfer dat zegt op hoeveel naburige cellen er een mijn staat.
Methoden waaraan een postie op het speelveld moet doorgegeven worden (isMijn, mijnToevoegen, mijnVerwijderen en naburigeMijnen), moeten telkens nagaan of deze positie binnen de grenzen van het speelveld valt. Indien dit niet het geval is, moet hiervoor een AssertionError opgeworpen worden met een passende foutboodschap zoals aangegeven in onderstaand voorbeeld.
>>> speelveld = Mijnenveger(4, 5, [(0, 0), (1, 2)])
>>> print(speelveld)
*2110
12*10
01110
00000
>>> speelveld.rijen()
4
>>> speelveld.kolommen()
5
>>> speelveld.isMijn(0, 0)
True
>>> speelveld.naburigeMijnen(1, 0)
1
>>> speelveld.naburigeMijnen(1, 1)
2
>>> speelveld.mijnToevoegen(3, 3)
>>> print(speelveld)
*2110
12*10
01221
001*1
>>> speelveld.mijnVerwijderen(0, 0)
>>> print(speelveld.isMijn(0, 0))
False
>>> print(speelveld)
01110
01*10
01221
001*1
>>> speelveld
Mijnenveger(4, 5, [(1, 2), (3, 3)])
>>> speelveld.isMijn(4, 4)
Traceback (most recent call last):
AssertionError: ongeldige positie (4, 4)
>>> speelveld = Mijnenveger(1, 1)
Traceback (most recent call last):
AssertionError: speelveld moet minstens twee rijen hebben
>>> speelveld = Mijnenveger(2, 1)
Traceback (most recent call last):
AssertionError: speelveld moet minstens twee kolommen hebben