Zeeslag is een spel voor twee spelers. Elke speler heeft een spelbord met twee helften, waarbij elke helft bstaat uit een $$10 \times 10$$ rooster van 10 rijen en 10 kolommen. Elke rij en elke kolom wordt gelabeld met één karakter. Alle rijen moeten een verschillend label hebben, alle kolommen moeten een verschillend label hebben, maar hetzelfde karakter kan wel als rijlabel en als kolomlabel gebruikt worden. Hieronder hebben we bijvoorbeeld de rijen van boven naar onder gelabeld met de eerste tien hoofdletters van het alfabet (ABCDEFGHIJ) en de kolommen van links naar rechts met de tien cijfers (0123456789). Een vakje in het rooster wordt aangeduid met de combinatie van een rijlabel en een kolomlabel, bijvoorbeeld A1 of B9 met de labels die we als voorbeeld gebruikt hebben.

zeeslage
De linkerhelft van het spelbord van een speler wordt het speelveld genoemd. Daarop mag een speler tien schepen plaatsen met een lengte die varieert van 2 tot 6 opeenvolgende vakjes. Schepen mogen horizontaal of verticaal staan, maar niet diagonaal. De schepen mogen elkaar horizontaal, verticaal en diagonaal niet raken. Verschillende types van schepen hebben elk een eigen lengte. Elke speler heeft een vloot van 10 schepen.
zeeslage
De rechterhelft van het spelbord van een speler wordt het volgrooster genoemd. Daarop maakt de speler aantekeningen over de ligging van de schepen in de vloot van de tegenstander.

Op het spelbord van een speler wordt de linkerhelft het speelveld genoemd. Daarop mag een speler tien schepen plaatsen met een lengte die varieert van 2 tot 6 opeenvolgende vakjes. Schepen mogen horizontaal of verticaal staan, maar niet diagonaal. De schepen mogen elkaar horizontaal, verticaal en diagonaal niet raken. Verschillende types van schepen hebben elk een eigen lengte. Elke speler heeft een vloot van 10 schepen:

aantal type lengte
vliegdekschip 6
slagschip 4
onderzeeër 3
patrouilleschip 2

De rechterhelft van het spelbord van een speler wordt het volgrooster genoemd. Daarop maakt de speler aantekeningen over de ligging van de schepen in de vloot van de tegenstander.

Nadat beide spelers hun schepen geplaatst hebben, verloopt het spel in een aantal ronden. Daarbij moeten de spelers om de beurt luidop de coördinaten zeggen van een vakje in het rooster van de tegenstander dat moet beschoten worden, bv. A1. De tegenstander geeft dan aan met hit of het vakje door een schip bezet wordt of met miss als dat niet het geval is. De aanvallende speler duidt die treffer (aangeduid met een rode cirkel in bovenstaande afbeeldingen) of misser (aangeduid met een witte cirkel in bovenstaande afbeeldingen) aan op zijn eigen volgrooster (rechterhelft), om de vloot van de tegenstander in kaart te brengen. De aangevallen speler duidt de treffer of misser aan op zijn speelveld (linkerhelft), om in kaart te brengen welke van zijn schepen geraakt werden. Doel is dat de spelers door gericht te raden proberen de posities van alle schepen van hun tegenstander proberen te achterhalen en zo de vloot van hun tegenstander tot zinken brengen.

Wanneer alle coördinaten van een schip geraakt zijn, is het schip gezonken. De eigenaar van het schip laat dat aan zijn tegenstander weten, bijvoorbeeld met de zin

Je hebt mijn slagschip laten zinken!

Zodra alle schepen in de vloot van een speler gezonken zijn, is het spel afgelopen en is zijn tegenstander gewonnen.

Opgave

Schrijf een bash shell script battleships waarmee een grafische voorstelling van schepen en schoten op een helft van een Zeeslag-spelbord kan uitgeschreven worden naar standaard uitvoer (stdout) in SVG-formaat. Aan het script moet de padnaam doorgegeven worden van een configuratiebestand dat de opstelling van de schepen en de coördinaten van de schoten beschrijft. Verderop1 bespreken we stap voor stap hoe deze SVG-afbeelding opgebouwd wordt. Het script moet bijvoorbeeld kunnen gebruikt worden om de twee afbeeldingen uit de inleiding van deze opgave te genereren. De linker afbeelding kan bijvoorbeeld gegenereerd worden op basis van dit configuratiebestand (configuration.txt2):

H2A0
V3A3
H4A6
V4C0
H3C5
V2C9
V2F5
V3F9
H2H0
H6J4
SD2
SE5
SE6
SF1
SF7
SF9
SG3
SG9
SH0
SH1
SH7

De eerste regels van het configuratiebestand bevatten vier karakters. Ze beschrijven de posities van de schepen. Regels die starten met een hoofdletter H beschrijven schepen die horizontaal liggen en regels die beginnen met een V beschrijven schepen die verticaal liggen. In beide gevallen is het tweede karakter de lengte van het schip. Het derde karakter is het rijlabel en het vierde karakter is het kolomlabel van het meest linkse vakje (voor horizontale schepen) of het bovenste vakje (voor verticale schepen).

De laatste regels van het configuratiebestand starten met de hoofdletter S en bevatten drie karakters. Ze beschrijven de coördinaten van de schoten. Het tweede karakter is het rijlabel en het derde karakter is het kolomlabel van het vakje waarop geschoten werd.

Als de optie -s gebruikt wordt, dan schrijft het script met voor het voorbeeld configuratiebestand de volgende SVG-afbeelding uit naar stdout (battleships.left.svg3):

<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="-1.5 -1.5 12 12">
<style type="text/css">
text{font-family:consolas;font-size:0.5px;text-anchor:middle;dominant-baseline:middle;fill:black}
.vertical{writing-mode:vertical-rl;text-orientation:upright}
.grid{stroke:black;stroke-width:10.025;stroke-dasharray:0.025 0.975}
.ship{stroke:rgba(187,187,187,0.9);stroke-width:0.8;stroke-linecap:round}
.shot{stroke:black;stroke-width:0.0125} .hit{fill:red} .miss{fill:white}
</style>
<rect x="-1.5" y="-1.5" width="12" height="12" fill="#DCE7FA" rx="0.2" />
<path class="grid" d="M-0.5125,4.5 L10.0125,4.5 M4.5,-0.5125 L4.5,10.0125" />
<text class="vertical" x="-1" y="4.5" textLength="9.6">ABCDEFGHIJ</text>
<text x="4.5" y="-.9" textLength="9.25">0123456789</text>
<line class="ship" x1="0" y1="0" x2="1" y2="0" />
<line class="ship" x1="3" y1="0" x2="3" y2="2" />
<line class="ship" x1="6" y1="0" x2="9" y2="0" />
<line class="ship" x1="0" y1="2" x2="0" y2="5" />
<line class="ship" x1="5" y1="2" x2="7" y2="2" />
<line class="ship" x1="9" y1="2" x2="9" y2="3" />
<line class="ship" x1="5" y1="5" x2="5" y2="6" />
<line class="ship" x1="9" y1="5" x2="9" y2="7" />
<line class="ship" x1="0" y1="7" x2="1" y2="7" />
<line class="ship" x1="4" y1="9" x2="9" y2="9" />
<circle class="shot miss" cx="2" cy="3" r="0.15" />
<circle class="shot miss" cx="5" cy="4" r="0.15" />
<circle class="shot miss" cx="6" cy="4" r="0.15" />
<circle class="shot miss" cx="1" cy="5" r="0.15" />
<circle class="shot miss" cx="7" cy="5" r="0.15" />
<circle class="shot hit" cx="9" cy="5" r="0.15" />
<circle class="shot miss" cx="3" cy="6" r="0.15" />
<circle class="shot hit" cx="9" cy="6" r="0.15" />
<circle class="shot hit" cx="0" cy="7" r="0.15" />
<circle class="shot hit" cx="1" cy="7" r="0.15" />
<circle class="shot miss" cx="7" cy="7" r="0.15" />
</svg>

Het script moet de volgende opties ondersteunen:

Het script moet voor de verwerking van de opties de flexibiliteit aan de dag leggen die gebruikelijk is bij Unix commando's: volgorde van opties speelt geen rol, opties kunnen samengenomen worden, argument bij een optie moet niet noodzakelijk van de optieletter gescheiden worden door witruimte, …. Daarnaast moet het script de volgende foutafhandeling voorzien:

Hierbij hebben we de foutafhandeling in volgorde van prioriteit opgelijst. De gepaste foutboodschappen vind je terug in onderstaand voorbeeld.

Opbouw van de SVG-afbeelding

Scalable Vector Graphics (SVG4) is een op XML5 gebaseerd bestandsformaat dat vectorafbeeldingen6 tekstueel beschrijft aan de hand van eenvoudige meetkundige bouwstenen zoals punten, lijnen, cirkels en veelhoeken.

Je moet geen SVG of XML kennen om deze opgave op te lossen. We bespreken stap voor stap hoe de grafische voorstelling van de schaakbordopstelling in SVG-formaat opgebouwd wordt aan de hand van vier sjablonen: hoofding7, schip8, schot9 en voettekst10. Daarbij zullen we de variabele onderdelen van elk sjabloon in het groen weergeven. Daar moet het script de juiste waarden invullen. Het is belangrijk dat elk sjabloon exact overgenomen wordt zodat het resultaat dat het script uitschrijft precies overeenkomt met de beschreven specificatie.

Hoofding
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="-1.5 -1.5 12 12">
<style type="text/css">
text{font-family:consolas;font-size:0.5px;text-anchor:middle;dominant-baseline:middle;fill:black}
.vertical{writing-mode:vertical-rl;text-orientation:upright}
.grid{stroke:black;stroke-width:10.025;stroke-dasharray:0.025 0.975}
.ship{stroke:rgba(187,187,187,0.9);stroke-width:0.8;stroke-linecap:round}
.shot{stroke:black;stroke-width:0.0125} .hit{fill:red} .miss{fill:white}
</style>
<rect x="-1.5" y="-1.5" width="12" height="12" fill="#DCE7FA" rx="0.2" />
<path class="grid" d="M-0.5125,4.5 L10.0125,4.5 M4.5,-0.5125 L4.5,10.0125" />
<text class="vertical" x="-1" y="4.5" textLength="9.6">ABCDEFGHIJ</text>
<text x="4.5" y="-.9" textLength="9.25">0123456789</text>

Dit sjabloon voor de hoofding bevat de SVG start-tag (svg) en algemene opmaak van de afbeelding (style). Het tekent ook een achtergrond (rect) en een $$10 \times 10$$ (path). De laatste twee regels (text) plaatsen respectievelijk de rij- en kolomlabels. Op de voorlaatste regel van het sjabloon moet de string met 10 unieke karakters ingevuld worden die als rijlabels gebruikt worden. Op de laatste regel van het sjabloon moet de string met 10 unieke karakters ingevuld worden die als kolomlabels gebruikt worden.

Schip
<line class="ship" x1="0" y1="0" x2="1" y2="0" />

Dit tekent een schip waarvan het meest linkse vakje (voor horizontale schepen) of het bovenste vakje (voor verticale schepen) gelegen is op rij x1 en kolom y1, en het meest rechtse vakje (voor horizontale schepen) of het onderste vakje (voor verticale schepen) gelegen is op rij x2 en kolom y2. Hierbij worden de rijen van het rooster van boven naar onder genummerd vanaf nul, en de kolommen van links naar rechts. De waarden waarmee we de vier attributen x1, y1, x2 en y2 ingevuld hebben in het sjabloon, plaatst bijvoorbeeld een horizontaal schip van lengte twee in de eerste twee vakjes op de bovenste rij (coördinaten A0 en A1 als we letters al rijlabels en cijfers als kolomlabels gebruiken; met die labels wordt dit schip in het configuratiebestand beschreven als H2A0).

Als het script wordt aangeroepen met de optie -s, dan moet voor elk schip in het configuratiebestand een afzonderlijke regel toegevoegd worden aan de SVG-afbeelding, die opgemaakt wordt volgens dit sjabloon. In de SVG-afbeelding moeten de schepen in dezelfde volgorde opgelijst worden als in het configuratiebestand.

Schot
<circle class="shot miss" cx="2" cy="3" r="0.15" />

Dit tekent het resultaat van een schot naar het vakje op rij cx en kolom cy, waarbij de rijen en de kolommen op dezelfde manier genummerd worden als bij de schepen. Voor een treffer moet de tweede klasse (class) ingesteld worden op hit, en voor een misser op miss. Het script moet op basis van de posities van de schepen bepalen of een schot een treffer of een misser is, ook als de schepen zelf niet getekend worden. De waarden waarmee we de drie attributen class, cx en cy ingevuld hebben in het sjabloon, plaatst bijvoorbeeld een misser in het vakje op de derde rij geteld vanaf de bovenkant van het rooster en de vierde kolom geteld vanaf de linkerkant van het rooster (met coördinaat D2 als we letters al rijlabels en cijfers als kolomlabels gebruiken; met die labels wordt dit schit in het configuratiebestand beschreven als SD2).

Voor elk schot in het configuratiebestand moet een afzonderlijke regel toegevoegd worden aan de SVG-afbeelding, die wordt opgemaakt volgens dit sjabloon. In de SVG-afbeelding moeten de schoten in dezelfde volgorde opgelijst worden als in het configuratiebestand, na de schepen (indien weergegeven).

Voettekst
</svg>

Dit laatste sjabloon sluit de SVG-afbeelding af met een SVG stop-tag.

Voorbeeld

Onderstaande voorbeeldsessie toont hoe het shell script battleships moet kunnen gebruikt worden.

$ battleships -r "ABCDEFGHIJ" -c "0123456789" configuration.txt11 > battleships.right.svg12
$ cat battleships.right.svg13
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="-1.5 -1.5 12 12">
<style type="text/css">
text{font-family:consolas;font-size:0.5px;text-anchor:middle;dominant-baseline:middle;fill:black}
.vertical{writing-mode:vertical-rl;text-orientation:upright}
.grid{stroke:black;stroke-width:10.025;stroke-dasharray:0.025 0.975}
.ship{stroke:rgba(187,187,187,0.9);stroke-width:0.8;stroke-linecap:round}
.shot{stroke:black;stroke-width:0.0125} .hit{fill:red} .miss{fill:white}
</style>
<rect x="-1.5" y="-1.5" width="12" height="12" fill="#DCE7FA" rx="0.2" />
<path class="grid" d="M-0.5125,4.5 L10.0125,4.5 M4.5,-0.5125 L4.5,10.0125" />
<text class="vertical" x="-1" y="4.5" textLength="9.6">ABCDEFGHIJ</text>
<text x="4.5" y="-.9" textLength="9.25">0123456789</text>
<circle class="shot miss" cx="2" cy="3" r="0.15" />
<circle class="shot miss" cx="5" cy="4" r="0.15" />
<circle class="shot miss" cx="6" cy="4" r="0.15" />
<circle class="shot miss" cx="1" cy="5" r="0.15" />
<circle class="shot miss" cx="7" cy="5" r="0.15" />
<circle class="shot hit" cx="9" cy="5" r="0.15" />
<circle class="shot miss" cx="3" cy="6" r="0.15" />
<circle class="shot hit" cx="9" cy="6" r="0.15" />
<circle class="shot hit" cx="0" cy="7" r="0.15" />
<circle class="shot hit" cx="1" cy="7" r="0.15" />
<circle class="shot miss" cx="7" cy="7" r="0.15" />
</svg>
$ battleships -s -r "ABCDEFGHIJ" -c "0123456789" configuration.txt14 > battleships.left.svg15
$ cat battleships.left.svg16
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="600" viewBox="-1.5 -1.5 12 12">
<style type="text/css">
text{font-family:consolas;font-size:0.5px;text-anchor:middle;dominant-baseline:middle;fill:black}
.vertical{writing-mode:vertical-rl;text-orientation:upright}
.grid{stroke:black;stroke-width:10.025;stroke-dasharray:0.025 0.975}
.ship{stroke:rgba(187,187,187,0.9);stroke-width:0.8;stroke-linecap:round}
.shot{stroke:black;stroke-width:0.0125} .hit{fill:red} .miss{fill:white}
</style>
<rect x="-1.5" y="-1.5" width="12" height="12" fill="#DCE7FA" rx="0.2" />
<path class="grid" d="M-0.5125,4.5 L10.0125,4.5 M4.5,-0.5125 L4.5,10.0125" />
<text class="vertical" x="-1" y="4.5" textLength="9.6">ABCDEFGHIJ</text>
<text x="4.5" y="-.9" textLength="9.25">0123456789</text>
<line class="ship" x1="0" y1="0" x2="1" y2="0" />
<line class="ship" x1="3" y1="0" x2="3" y2="2" />
<line class="ship" x1="6" y1="0" x2="9" y2="0" />
<line class="ship" x1="0" y1="2" x2="0" y2="5" />
<line class="ship" x1="5" y1="2" x2="7" y2="2" />
<line class="ship" x1="9" y1="2" x2="9" y2="3" />
<line class="ship" x1="5" y1="5" x2="5" y2="6" />
<line class="ship" x1="9" y1="5" x2="9" y2="7" />
<line class="ship" x1="0" y1="7" x2="1" y2="7" />
<line class="ship" x1="4" y1="9" x2="9" y2="9" />
<circle class="shot miss" cx="2" cy="3" r="0.15" />
<circle class="shot miss" cx="5" cy="4" r="0.15" />
<circle class="shot miss" cx="6" cy="4" r="0.15" />
<circle class="shot miss" cx="1" cy="5" r="0.15" />
<circle class="shot miss" cx="7" cy="5" r="0.15" />
<circle class="shot hit" cx="9" cy="5" r="0.15" />
<circle class="shot miss" cx="3" cy="6" r="0.15" />
<circle class="shot hit" cx="9" cy="6" r="0.15" />
<circle class="shot hit" cx="0" cy="7" r="0.15" />
<circle class="shot hit" cx="1" cy="7" r="0.15" />
<circle class="shot miss" cx="7" cy="7" r="0.15" />
</svg>
$ battleships -x configuration.txt17
Syntax: solution.nl.sh [-r <labels>] [-c <labels>] [-s] <file>
$ echo $?
1
$ battleships -r ABC configuration.txt18
solution.nl.sh: invalid row labels: ABC
$ echo $?
2
$ battleships -c XXXXXXXXXX configuration.txt19
solution.nl.sh: invalid column labels: XXXXXXXXXX
$ echo $?
2
$ battleships 
Syntax: solution.nl.sh [-r <labels>] [-c <labels>] [-s] <file>
$ echo $?
3
$ battleships unknown
solution.nl.sh: invalid file: unknown
$ echo $?
4