De meeste legpuzzels kun je slechts één keer oplossen en dan ben je klaar. Maar de Daily Calendar Puzzle1 is een ontzettend leuke en verslavende puzzel die elke dag een nieuwe uitdaging in petto heeft. Al wat je moet doen is de tien puzzelstukken in het kalenderframe leggen zodat in de drie uitsparingen een weekdag, een dag en een maand overblijven.
Het zou een leugen zijn om te zeggen dat alle datums makkelijk te vormen zijn, maar voor elke dag is er minstens één oplossing te vinden. Kun je de datum van vandaag leggen? Je verjaardag? Een historische gebeurtenis?
Het kalenderframe van een Daily Calendar Puzzle bestaat uit een $$8 \times 7$$ rooster met 8 rijen en 7 kolommen. De 56 vierkante vakjes in het rooster krijgen elk een volgnummer (Number) van 0 tot en met 55 door het rooster van links naar rechts en van boven naar onder te overlopen. Er zijn zes vakjes waarop geen puzzelstuk kan gelegd worden: 6, 13, 49, 50, 51 en 52 (hieronder weergegeven met een donkerder achtergrondkleur). Alle andere vakjes hebben een uniek label: achtereenvolgens (op volgnummer) de Engelse drie-letter-afkortingen voor de twaalf maanden van het jaar (Jan–Dec), de dagnummers (1–31) en de Engelse drie-letter afkortingen voor de zeven weekdagen (Sun–Sat).
Elk puzzelstuk krijgt als label een unieke hoofdletter (String) om het van andere puzzelstukken te kunnen onderscheiden. De plaats waar een puzzelstuk in het frame neergelegd wordt, stellen we voor als een array (Array) met de volgnummers van de vakjes die door het puzzelstuk bedekt worden. Dit beschrijft meteen ook de vorm van het puzzelstuk. Hieronder hebben we bijvoorbeeld een recht oranje puzzelstuk neergelegd op plaats [0, 1, 2, 3], waardoor het de vier vakjes aan de linkerkant van de bovenste rij bedekt.
Definieer een klasse Kalender waarmee het frame van een Daily Calendar Puzzle kan voorgesteld worden en waarin puzzelstukken kunnen neergelegd en weggenomen worden. Bij het aanmaken van een nieuw frame (Kalender) moeten er geen argumenten doorgegeven worden: elk frame heeft dezelfde afmetingen en bij aanvang liggen er nog geen puzzelstukken in het frame. Op een frame $$f$$ (Kalender) moeten minstens de volgende methoden kunnen aangeroepen worden:
Een methode vakje waaraan het volgnummer $$n$$ (Number) van een vakje moet doorgegeven worden. De methode moet een stringvoorstelling (String) van vakje $$n$$ teruggeven die bestaat uit drie tekens:
###: als er op vakje $$n$$ geen puzzelstukken kunnen gelegd worden
#?#: als vakje $$n$$ door een puzzelstuk bedekt wordt in frame $$f$$, met het label van het puzzelstuk op de plaats van het vraagteken
???: als vakje $$n$$ niet door een puzzelstuk bedekt wordt in frame $$f$$, met het label van het vakje op de plaats van de drie vraagtekens; labels de uit minder dan drie tekens bestaan, worden links opgevuld met spaties; de Engelse drie-letter-afkortingen voor de twaalf maanden zijn
Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
en de Engelse drie-letter-afkortingen voor de zeven weekdagen zijn
Sun,Mon,Tue,Wed,Thu,Fri,Sat
Een methode toString waaraan geen argumenten moeten doorgegeven worden. De methode moet een stringvoorstelling (String) van frame $$f$$ teruggeven. Daarin vormt elke rij een afzonderlijke regel met de stringvoorstelling van de vakjes op die rij in frame $$f$$, opgelijst van links naar rechts en telkens van elkaar gescheiden door één spatie.
Een methode puzzelstuk waaraan het label $$l$$ van een puzzelstuk moet doorgegeven worden. Als er in frame $$f$$ geen puzzelstuk met label $$l$$ ligt, dan moet een AssertionError opgeworpen worden met de boodschap puzzelstuk ? ligt er niet, waarbij label $$l$$ op de plaats van het vraagteken komt. Anders moet de methode een nieuwe array (Array) teruggeven met de volgnummers van alle vakjes die in frame $$f$$ bedekt worden door het puzzelstuk met label $$l$$. De volgnummers moeten daarbij van klein naar groot opgelijst worden.
Een methode neerleggen waaraan het label $$l$$ en de plaats $$p$$ van een puzzelstuk moeten doorgegeven worden. Als er in frame $$f$$ reeds een puzzelstuk met label $$l$$ ligt, dan moet een AssertionError opgeworpen worden met de boodschap puzzelstuk ? ligt er al. Als het puzzelstuk niet op plaats $$p$$ kan neergelegd worden in frame $$f$$, dan moet een AssertionError opgeworpen worden met de boodschap puzzelstuk ? kan niet gelegd worden. In beide gevallen komt label $$l$$ op de plaats van het vraagteken. Anders moet frame $$f$$ bijhouden dat er een puzzelstuk met label $$l$$ neergelegd werd op plaats $$p$$, en moet de methode een verwijzing naar frame $$f$$ teruggeven.
Een methode wegnemen waaraan het label $$l$$ van een puzzelstuk moet doorgegeven worden. Als er in frame $$f$$ geen puzzelstuk met label $$l$$ ligt, dan moet een AssertionError opgeworpen worden met de boodschap puzzelstuk ? ligt er niet, waarbij label $$l$$ op de plaats van het vraagteken komt. Anders moet het puzzelstuk met label $$l$$ van frame $$f$$ weggenomen worden, en moet de methode een verwijzing naar frame $$f$$ teruggeven.
Een methode uitlezen waaraan geen argumenten moeten doorgegeven worden. Als er in frame $$f$$ exact drie vakjes zijn waarop geen puzzelstuk ligt en die hebben respectievelijk het label van een weekdag $$w$$, een dagnummer $$d$$ en een maand $$m$$, dan moet een string (String) teruggeven worden met weekdag $$m$$ gevolgd door een komma, een spatie, dagnummer $$d$$ (zonder extra spaties vooraan), nog een spatie en maand $$m$$. Anders moet een AssertionError opworpen worden met de boodschap ongeldige datum.
> const frame = new Kalender();
> frame.vakje(0)
"Jan"
> frame.vakje(39)
" 26"
> frame.vakje(48)
"Wed"
> frame.vakje(52)
"###"
> console.log(frame.toString())
Jan Feb Mar Apr May Jun ###
Jul Aug Sep Oct Nov Dec ###
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
> frame.puzzelstuk("A")
AssertionError: puzzelstuk A ligt er niet
> console.log(frame.neerleggen("A", [0, 1, 2, 3]).toString())
#A# #A# #A# #A# May Jun ###
Jul Aug Sep Oct Nov Dec ###
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
> frame.puzzelstuk("A")
[0, 1, 2, 3]
> console.log(frame.neerleggen("B", [4, 5, 9, 10, 11]).toString())
#A# #A# #A# #A# #B# #B# ###
Jul Aug #B# #B# #B# Dec ###
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
> frame.neerleggen("B", [7, 8, 14, 21, 28])
AssertionError: puzzelstuk B ligt er al
> frame.neerleggen("C", [13, 14, 15])
AssertionError: puzzelstuk C kan niet gelegd worden
> frame.neerleggen("C", [0, 1, 7, 8])
AssertionError: puzzelstuk C kan niet gelegd worden
> console.log(frame.neerleggen("C", [7, 8, 14, 21, 28]).neerleggen("D", [15, 16, 22, 23, 29]).toString())
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# 4 5 6 7
#C# #D# #D# 11 12 13 14
#C# #D# 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
> console.log(frame.wegnemen("A").toString())
Jan Feb Mar Apr #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# 4 5 6 7
#C# #D# #D# 11 12 13 14
#C# #D# 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
> frame.wegnemen("Z")
AssertionError: puzzelstuk Z ligt er niet
> console.log(frame.neerleggen("A", [0, 1, 2, 3]).toString())
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# 4 5 6 7
#C# #D# #D# 11 12 13 14
#C# #D# 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
> console.log(frame.neerleggen("E", [17, 24, 25, 26, 33]).neerleggen("F", [18, 19, 20, 27, 34]).toString())
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# #E# #F# #F# #F#
#C# #D# #D# #E# #E# #E# #F#
#C# #D# 17 18 19 #E# #F#
22 23 24 25 26 27 28
29 30 31 Sun Mon Tue Wed
### ### ### ### Thu Fri Sat
> console.log(frame.neerleggen("G", [30, 31, 32, 38, 45]).neerleggen("H", [35, 36, 37, 42, 44]).toString())
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# #E# #F# #F# #F#
#C# #D# #D# #E# #E# #E# #F#
#C# #D# #G# #G# #G# #E# #F#
#H# #H# #H# #G# 26 27 28
#H# 30 #H# #G# Mon Tue Wed
### ### ### ### Thu Fri Sat
> frame.uitlezen()
AssertionError: ongeldige datum
> console.log(frame.neerleggen("I", [39, 40, 41, 48]).neerleggen("J", [46, 47, 54, 55]).toString())
#A# #A# #A# #A# #B# #B# ###
#C# #C# #B# #B# #B# Dec ###
#C# #D# #D# #E# #F# #F# #F#
#C# #D# #D# #E# #E# #E# #F#
#C# #D# #G# #G# #G# #E# #F#
#H# #H# #H# #G# #I# #I# #I#
#H# 30 #H# #G# #J# #J# #I#
### ### ### ### Thu #J# #J#
> frame.uitlezen()
"Thu, 30 Dec"
Er zijn 28 972 628 verschillende manieren waarop de 10 puzzelstukken in het rooster kunnen gelegd worden. Daarvan zijn er 4 937 780 (17.04 %) die een maand, een dag en een weekdag open laten en 4 864 096 (16.79 %) als we daaruit ook nog ongeldige datums zoals 30 februari of 31 november schrappen. De makkelijkste datum is dinsdag 7 juni (Tue, 7 Jun): die kan op 10 374 verschillende manieren gevormd worden. De moeilijkste datum is maandag 6 april (Mon, 6 Apr): die kan maar op 97 verschillende manieren gevormd worden.
Mocht je zelf verslaafd raken aan deze puzzel: dit is een overzicht2 met een willekeurig geselecteerde oplossing voor elk van de $$12 \times 31 \times 7$$ verschillende dagen van het jaar. Daarmee kan je alvast je frustraties opbergen op dagen waar je er zelf niet aan uitraakt.
Onder de naam dageraat3 werd er ook een versie van de puzzel met zeshoekige vakjes uitgebracht.