Een kruiswoordraadsel is een woordpuzzel die je vaak in kranten, tijdschriften en puzzelboeken vindt. De puzzel wordt opgelost door woorden in te vullen in een rechthoekig rooster van witte en zwarte vakjes. Daarbij worden de woorden horizontaal (van links naar rechts) en verticaal (van boven naar onder) ingevuld in opeenvolgende witte vakjes — één letter per vakje. De rijen en kolommen van het rooster worden gelabeld. Bij het label van alle rijen en kolommen wordt voor elk gezocht woord een omschrijving gegeven. Voor, tussen en na een woord staan zwarte vakjes, zodat je op voorhand weet hoe lang elk woord is.

De oplossing van een kruiswoordraadsel met een $$m \times n$$ rooster met $$m$$ rijen en $$n$$ kolommen wordt opgeslagen in een tekstbestand dat bestaat uit $$m$$ regels die elk $$n$$ karakters bevatten. Die karakters zijn ofwel hoofdletters die in de witte vakjes komen of hekjes (#) die de zwarte vakjes aanduiden. Dit is bijvoorbeeld de inhoud van een bestand met de oplossing van een $$12 \times 12$$ kruiswoordraadsel.

ZELFSTANDIGE
AMULET#ARE#I
COCON#IDOLEN
HERPES#EET#D
T#A#GLEED#IR
MITRAILLETTE
OLIELEK#LEED
ESEN#REIS#RA
DEVENTER#SEC
I#ESA#NADERT
GE#SAS#KO#EI
ENGERDS#GENE

Hieronder zie je een grafische weergave van dit kruiswoordraadsel: links de opgave van de puzzel met lege witte vakjes die nog moeten ingevuld worden en rechts de oplossing van de puzzel waarbij in elk wit vakje een hoofdletter ingevuld is.

puzzel
Opgave van een kruiswoordraadsel met een $$12 \times 12$$ rooster. De rijen en kolommen worden gelabeld met volgnummers. Voor de labels wordt het lettertype Verdana gebruikt.
puzzel
Oplossing van een kruiswoordraadsel met een $$12 \times 12$$ rooster. De rijen en kolommen worden gelabeld met opeenvolgende letters van het alfabet. Voor de labels wordt het lettertype Times New Roman gebruikt.

Opgave

Schrijf een bash shell script crossword waarmee een grafische weergave van de opgave of de oplossing van een kruiswoordraadsel kan getekend worden. Aan het script moet de padnaam doorgegeven worden van een tekstbestand met de oplossing van het kruiswoordraadsel. Het script moet de grafische voorstelling van het kruiswoordraadsel in SVG-formaat uitschrijven naar stdout. Zo moet het script voor dit bestand

DE#
EN#
#E#

bijvoorbeeld de volgende SVG-afbeelding uitschrijven

<svg xmlns="http://www.w3.org/2000/svg" height="82" width="82" version="1.1">
<defs><style type="text/css">
text { font-family: Verdana; font-size: 12px; fill: black; }
text { dominant-baseline: central; text-anchor: middle; }
rect { height: 20px; width: 20px; stroke: black; }
</style></defs>
<text x="30" y="10">1</text>
<text x="50" y="10">2</text>
<text x="70" y="10">3</text>
<text x="10" y="30">1</text>
<rect x="20" y="20" fill="white" />
<text x="30" y="30">D</text>
<rect x="40" y="20" fill="white" />
<text x="50" y="30">E</text>
<rect x="60" y="20" fill="black" />
<text x="10" y="50">2</text>
<rect x="20" y="40" fill="white" />
<text x="30" y="50">E</text>
<rect x="40" y="40" fill="white" />
<text x="50" y="50">N</text>
<rect x="60" y="40" fill="white" />
<text x="70" y="50">E</text>
<text x="10" y="70">3</text>
<rect x="20" y="60" fill="black" />
<rect x="40" y="60" fill="white" />
<text x="50" y="70">E</text>
<rect x="60" y="60" fill="black" />
</svg>

Verderop1 bespreken we stap voor stap hoe deze SVG-afbeelding opgebouwd wordt, maar grafisch ziet ze er als volgt uit:

1 2 3 1 D E 2 E N E 3 E

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:

Opbouw van de SVG-afbeelding

Scalable Vector Graphics (SVG) is een op XML2 gebaseerd bestandsformaat voor vectorafbeeldingen3. Daarin worden afbeeldingen tekstueel beschreven aan de hand van eenvoudige meetkundige bouwstenen zoals tekst, punten, lijnen, cirkels en veelhoeken.

Je moet geen SVG kennen om deze opgave te kunnen oplossen. We bespreken stap voor stap aan de hand van een aantal sjablonen hoe de grafische voorstelling van een kruiswoordraadsel in SVG-formaat moet opgebouwd worden. Daarbij zullen we de variabele onderdelen van een sjabloon in het groen weergeven. Daar moet je dus zelf 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.

Als skelet voor de SVG-afbeelding gebruiken we dit sjabloon:

<svg xmlns="http://www.w3.org/2000/svg" height="82" width="82" version="1.1">
<defs><style type="text/css">
text { font-family: Verdana; font-size: 12px; fill: black; }
text { dominant-baseline: central; text-anchor: middle; }
rect { height: 20px; width: 20px; stroke: black; }
</style></defs>
…
</svg>

Op de eerste regel worden de hoogte (height) en de breedte (width) van de afbeelding ingesteld. Een kruiswoordraadsel met een $$m \times n$$ rooster krijgt aan de linkerkant een extra kolom met rijlabels en aan de bovenkant een extra rij met kolomlabels. De hoogte van de afbeelding wordt dan berekend als $$20 \times (m + 1) + 2$$ en de breedte als $$20 \times (n + 1) + 2$$. De $$+ 2$$ op het einde van deze formules zorgt voor wat extra marge aan de rechterkant en onderaan. Op de derde regel wordt het lettertype (font-family) ingesteld voor de labels en de hoofdletters in de witte vakjes. Alle andere componenten van de afbeelding komen op de plaats waar de drie puntje (…) staan.

Voor het tekenen van het kruiswoordraadsel zelf, gebruiken we als bouwsteen dit sjabloon voor het tekenen van een vierkant met een witte of een zwarte achtergrond, met in het midden daarin een karakter: een cijfer of een hoofdletter.

<rect x="40" y="20" fill="white" />
<text x="50" y="30">E</text>

De rect-tag op de eerste regel tekent een vierkant met hoogte en breedte gelijk aan 20 en een zwarte rand. Die eigenschappen van het vierkant worden vastgelegd in het skelet voor de SVG-afbeelding. Om het vierkant op de juiste plaats te zetten, moeten we de $$x$$-coördinaat (x) en de $$y$$-coördinaat (y) van de linkerbovenhoek van het vierkant opgeven. Met het argument fill wordt de achtergrondkleur van het vierkant ingesteld: wit (white) of zwart (black).

De text-tag op de tweede regel tekent het karakter in het midden van het vierkant. Het karakter moet tussen de start- en eind-tag geplaatst worden (hier E als voorbeeld). Voor een vierkant met linkerbovenhoek op positie $$(x, y)$$, wordt met de argumenten x en y de positie van de tekst ingesteld op resp. $$x + 10$$ en  $$y + 10$$.

Onderstaande figuur gebruikt het $$3 \times 3$$ kruiswoordraadsel van hierboven als voorbeeld om te tonen hoe de vierkanten en karakters met dit sjabloon moeten getekend worden. Hierbij is ook weer te zien dat het rooster werd uitgebreid met een extra kolom links voor de rijlabels en een extra rij bovenaan voor de kolomlabels.

puzzel
SVG-afbeelding met coördinatenstelsel met oorsprong in de linkerbovenhoek, X-as die loopt van links naar rechts en Y-as die loopt van boven naar onder. Het rooster van het kruiswoordraadsel wordt uitgebreid met een extra kolom links voor de rijlabels en een extra rij bovenaan voor de kolomlabels. Bij het tekenen van de vierkanten en karakters worden de vakjes van dit uitgebreide rooster in de gebruikelijke leesrichting afgelopen: van links naar rechts en van boven naar onder.

Het coördinatenstelsel van SVG-afbeeldingen heeft haar oorsprong $$(0, 0)$$ in de linkerbovenhoek van de afbeelding, een X-as die loopt van links naar rechts en een Y-as die loopt van boven naar onder. De oranje pijl geeft aan dat we de vakjes van het uitgebreide rooster in de gebruikelijke leesvolgorde doorlopen: van links naar rechts en van boven naar onder. Voor elk vakje gebruiken we het sjabloon om een vierkant en een karakter te tekenen, maar het is niet altijd nodig om beide te tekenen. Soms moet enkel het vierkant getekend worden (eerste regel van het sjabloon) en soms moet enkel het karakter getekend worden (tweede regel van het sjabloon). De vakjes waarvoor geen vierkant moet getekend worden, hebben we in de figuur aangeduid door een lichtblauw vierkant met een gestippelde rand.

Voorbeeld

Onderstaande voorbeeldsessie toont hoe het shell script crossword moet kunnen gebruikt worden. Hierbij gaan we ervan uit dat de huidige directory het bestand solution.txt4 bevat, en geen bestand unknown.txt bevat.

$ crossword solution.txt5 > puzzle.svg
$ echo $?
0
$ cat puzzle.svg
<svg xmlns="http://www.w3.org/2000/svg" height="82" width="82" version="1.1">
<defs><style type="text/css">
text { font-family: Verdana; font-size: 12px; fill: black; }
text { dominant-baseline: central; text-anchor: middle; }
rect { height: 20px; width: 20px; stroke: black; }
</style></defs>
<text x="30" y="10">1</text>
<text x="50" y="10">2</text>
<text x="70" y="10">3</text>
<text x="10" y="30">1</text>
<rect x="20" y="20" fill="white" />
<rect x="40" y="20" fill="white" />
<rect x="60" y="20" fill="black" />
<text x="10" y="50">2</text>
<rect x="20" y="40" fill="white" />
<rect x="40" y="40" fill="white" />
<rect x="60" y="40" fill="white" />
<text x="10" y="70">3</text>
<rect x="20" y="60" fill="black" />
<rect x="40" y="60" fill="white" />
<rect x="60" y="60" fill="black" />
</svg>
$ crossword -sa -f monospace solution.txt6
<svg xmlns="http://www.w3.org/2000/svg" height="82" width="82" version="1.1">
<defs><style type="text/css">
text { font-family: monospace; font-size: 12px; fill: black; }
text { dominant-baseline: central; text-anchor: middle; }
rect { height: 20px; width: 20px; stroke: black; }
</style></defs>
<text x="30" y="10">A</text>
<text x="50" y="10">B</text>
<text x="70" y="10">C</text>
<text x="10" y="30">A</text>
<rect x="20" y="20" fill="white" />
<text x="30" y="30">D</text>
<rect x="40" y="20" fill="white" />
<text x="50" y="30">E</text>
<rect x="60" y="20" fill="black" />
<text x="90" y="30">
</text>
<text x="10" y="50">B</text>
<rect x="20" y="40" fill="white" />
<text x="30" y="50">E</text>
<rect x="40" y="40" fill="white" />
<text x="50" y="50">N</text>
<rect x="60" y="40" fill="white" />
<text x="70" y="50">E</text>
<text x="90" y="50">
</text>
<text x="10" y="70">C</text>
<rect x="20" y="60" fill="black" />
<rect x="40" y="60" fill="white" />
<text x="50" y="70">E</text>
<rect x="60" y="60" fill="black" />
<text x="90" y="70">
</text>
</svg>
$ crossword -X solution.txt7
Syntax: crossword [-s] [-a] [-f <font>] FILE
$ echo $?
1
$ crossword
Syntax: crossword [-s] [-a] [-f <font>] FILE
$ echo $?
2
$ crossword unknown.txt
Syntax: crossword [-s] [-a] [-f <font>] FILE
$ echo $?
3

Dit is de grafische voorstelling van de kruiswoordraadsels die door de eerste twee commando's gegenereerd worden:

1 2 3 1 2 3 A B C A D E B E N E C E