Bij het spelletje Codenames nemen twee rivaliserende geheime diensten het tegen elkaar op: team rood tegen team blauw.

Het spelbord bestaat uit een aantal kaarten die open op tafel gelegd worden in een rechthoekig $$m \times n$$ rooster met $$m$$ rijen en $$n$$ kolommen. Er bestaan twee varianten van het spel: één waarbij er tekeningen op de kaarten staan (zoals in onderstaand $$4 \times 5$$ rooster aan de linkerkant) en één waarbij er woorden op de kaarten staan (zoals in onderstaand $$5 \times 5$$ rooster aan de rechterkant). Daarmee zie je meteen dat het aantal rijen en kolommen van het spelbord kan variëren.

rooster tekeningen
Variant van Codenames waarbij er tekeningen staan op de kaarten die het spelbord vormen.
rooster woorden
Variant van Codenames waarbij er woorden staan op de kaarten die het spelbord vormen.

Elk team kiest een speler als hoofd van de geheime dienst. Deze twee spelers nemen plaats aan één kant van de tafel. Hun teamgenoten gaan aan de andere kant van de tafel zitten en zijn geheim agenten die moeten raden waar de spionnen van hun team zich bevinden.

De positie van de spionnen op het spelbord is enkel gekend voor de hoofden van de geheime diensten en staat vermeld op de sleutel van het spel. De sleutel komt overeen met de kaarten van het spelbord en vormt dus zelf ook een $$m \times n$$ rooster met hetzelfde aantal rijen en kolommen als het spelbord. Blauwe vakjes bepalen de plaatjes die het blauwe team (blauwe spionnen) moet vinden. Rode vakjes bepalen de plaatjes die het rode team (rode spionnen) moet vinden. Witte vakjes zijn onschuldige omstanders en het zwarte vakje is de huurmoordenaar waarmee nooit contact mag gezocht worden.

sleutel
Sleutel voor een spelletje Codenames met een $$4 \times 5$$ rooster (rood begint).

In een sleutel is er altijd juist één zwart vakje en is het verschil tussen het aantal blauwe en rode vakjes exact gelijk aan één. Het team dat het meeste spionnen moet vinden komt als eerste aan de beurt. In bovenstaande sleutel zijn er bijvoorbeeld 7 blauwe vakjes en 8 rode vakjes, waardoor het rode team als eerste aan de beurt komt.

Een team dat aan de beurt is, kan één van twee mogelijke acties ondernemen: een kaart aanduiden of stoppen. In dat laatste geval komt het andere team aan de beurt. In het eerste geval verklapt het hoofd van de geheime dienst de kleur van het vakje uit de sleutel waarmee de aangeduide kaart overeenkomt (door er een speciale kaart van die kleur op te leggen). De kleur van dat vakje bepaalt welk team als volgende aan de beurt komt: als de kleur van de aangeduide kaart overeenkomt met de kleur van het team dat aan de beurt was, dan blijft het team aan de beurt; anders komt het andere team aan de beurt.

Het spel eindigt als alle spionnen van een team aangeduid zijn of als de huurmoordenaar (zwart vakje) aangeduid wordt. In het eerste geval wint het team waarvan alle spionnen aangeduid werden. In het tweede geval verliest het team dat de huurmoordenaar aangeduid heeft.

Het hoofd van een geheime dienst kan tijdens het spel ook tips geven waarmee zijn team kan achterhalen welke kaarten op het spelbord overeenkomen met de kleur van het team in de sleutel. Hoe het geven van tips precies werkt, is niet relevant voor deze opgave en wordt uitgelegd in de spelregels (tekeningen1, woorden2).

Opgave

In het rooster van een spelbord en een sleutel worden de rijen van boven naar onder genummerd, en de kolommen van links naar rechts, telkens vanaf nul. Hierdoor kan de positie van een kaart of een vakje voorgesteld worden als een koppel $$(r, k)$$, waarbij $$r$$ (int) het rijnummer aanduidt en $$k$$ (int) het kolomnummer.

Een sleutel wordt voorgesteld als een string (str) waarvan de opeenvolgende karakters de kleuren van de vakjes aanduiden: een hoofdletter B voor een blauw vakje, een hoofdletter R voor een rood vakje, een koppelteken (-) voor een wit vakje en een hoofdletter X voor een zwart vakje met de huurmoordenaar. Hierbij worden de vakjes van de sleutel van links naar rechts en van boven naar onder doorlopen.

Definieer een klasse Spelbord waarmee het spelbord van een spelletje Codenames kan voorgesteld worden. Bij het aanmaken van een spelbord (Spelbord) moeten drie argumenten doorgegeven worden: i) het aantal rijen $$m$$ (int) van het rooster, ii) het aantal kolommen $$n$$ (int) van het rooster en iii) een sleutel (str). Hierbij mag ervan uitgegaan worden dat er een geldige configuratie van een spelbord en een sleutel doorgegeven wordt, zonder dat dit expliciet moet gecontroleerd worden.

De klasse Spelbord moet minstens de volgende methoden ondersteunen:

Als het spel op een spelbord (Spelbord) reeds afgelopen is, dan moet bij het aanroepen van de methoden beurt, stoppen en aanduiden een AssertionError opgeworpen worden met de boodschap spel is afgelopen.

Als er een spelbord (Spelbord) wordt doorgegeven aan de ingebouwde functie str dan moet een stringvoorstelling (str) van het spelbord teruggegeven worden, waarvan het formaat kan afgeleid worden uit onderstaand voorbeeld. Daarbij wordt een kaart die nog niet werd aangeduid voorgesteld door een vraagteken (?) en een kaart die wel al werd aangeduid door het karakter van het corresponderende vakje in de voorstelling van de sleutel. De voorstellingen van twee naburige vakjes worden telkens van elkaar gescheiden door één enkele spatie.

Voorbeeld

>>> codenames = Spelbord(4, 5, 'RRBBBRBRRBB-XR-B-RR-')
>>> print(codenames)
? ? ? ? ?
? ? ? ? ?
? ? ? ? ?
? ? ? ? ?
>>> codenames.beurt()
'R'
>>> codenames.isafgelopen()
False
>>> print(codenames.aanduiden(2, 3))
? ? ? ? ?
? ? ? ? ?
? ? ? R ?
? ? ? ? ?
>>> codenames.beurt()
'R'
>>> codenames.stoppen().beurt()
'B'
>>> codenames.isafgelopen()
False
>>> print(codenames.aanduiden(2, 1))
? ? ? ? ?
? ? ? ? ?
? - ? R ?
? ? ? ? ?
>>> codenames.beurt()
'R'
>>> print(codenames.aanduiden(3, 4))
? ? ? ? ?
? ? ? ? ?
? - ? R ?
? ? ? ? -
>>> codenames.beurt()
'B'
>>> print(codenames.aanduiden(1, 1).aanduiden(3, 0).aanduiden(0, 2).stoppen())
? ? B ? ?
? B ? ? ?
? - ? R ?
B ? ? ? -
>>> codenames.beurt()
'R'
>>> codenames.isafgelopen()
False
>>> print(codenames.aanduiden(2, 2))
? ? B ? ?
? B ? ? ?
? - X R ?
B ? ? ? -
>>> codenames.isafgelopen()
True
>>> codenames.beurt()
Traceback (most recent call last):
AssertionError: spel is afgelopen
>>> codenames.stoppen()
Traceback (most recent call last):
AssertionError: spel is afgelopen
>>> codenames.aanduiden(1, 4)
Traceback (most recent call last):
AssertionError: spel is afgelopen