17
\$\begingroup\$

Let's build an advent Calendar for this Christmas!

Many Advent calendars for children take the form of a large rectangular card with numbered windows for each day of December starting from 1 ( although advent time begins on Advent Sunday, which is the fourth Sunday before Christmas day and often happens to be on November) and leading up to and including Christmas Eve (24) or in some cases Christmas day (25).
Windows are distributed across the calendar at a random position.
Every day children open the corresponding calendar window to reveal a small gift(usually a chocolate)

Task

The task is to display an advent calendar up to date following these rules:

  • Include Christmas day : 1 to 25
  • For days that have passed put an opened empty window [ ] ( no different characters allowed ), so if i run the same code the next day there must be a new window opened.
  • At the current day put an opened window with a gift in it [@] ( instead of @ you can use every printable character excep for space, [ , ] and digits ).
  • For the remaining days put only the day number.
  • At Christmas all the windows will be open, the next day they are all closed ( a new calendar ).
  • Windows must open only on December, windows must stay closed on every month except for December.
  • It should work the same way the next year, and every year, that is for example on December 2099 Windows start opening.
  • Windows are laid down in random order on a 5 rows x 5 columns grid.
  • The grid has not to be accurate, just consider that every windows must have at least one character space separating them on every side: left, top, right and bottom hence numbers must have left and right spaces of at least two characters.

Example

Today (15 December) :



  23   21   [ ]   18   [ ]   

  [ ]   [ ]   [ ]   [ ]   16   

  24   22   [ ]   17   [ ]   

  [ ]   [ ]   [ ]   25   19   

  [ ]   [ ]   [@]   [ ]   20

Tomorrow (16 December) :



  [ ]   17   25   [ ]   [ ]   

  [ ]   19   [ ]   18   21   

  [ ]   23   24   [ ]   [ ]   

  22   [@]   [ ]   [ ]   [ ]   

  [ ]   20   [ ]   [ ]   [ ]

On every month different than December

18 23 21 12 19
25 24 3 15 14
16 9 17 20 4
8 2 11 5 7
6 13 10 22 1

Live example

All rules apply.

\$\endgroup\$
  • 1
    \$\begingroup\$ Is it supposed to work as well for the next coming years or only for this year? (I suppose it's possible to golf a few bytes on some implementations by assuming a 2019 timestamp.) \$\endgroup\$ – Arnauld Dec 15 '19 at 0:41
  • 1
    \$\begingroup\$ So this was in the Sandbox for… 8 minutes? \$\endgroup\$ – Adám Dec 15 '19 at 0:43
  • 2
    \$\begingroup\$ @Adám sorry but Christmas is just around the corner, since there was no Christmas challenge I felt a bit sad, I used the sandbox to check if my script worked but as I expected it didn't. This is an exception, I'll continue to use the sandbox in the future. \$\endgroup\$ – AZTECCO Dec 15 '19 at 0:57
  • 3
    \$\begingroup\$ @Jo King I don't want to see only open windows, at this time we have a good open/closed ratio \$\endgroup\$ – AZTECCO Dec 15 '19 at 1:06
  • 2
    \$\begingroup\$ Earlier this year, I saw an Advent calendar where the gift was a different bottle of wine, so it's not just for kids to enjoy the gifts! \$\endgroup\$ – Giuseppe Dec 15 '19 at 1:59

11 Answers 11

9
\$\begingroup\$

APL (Dyalog Extended), 57 bytesSBCS

Full program.

5 5⍴('[ ]'¨@{⍵∊⍳d}'[@]'¨@{⍵=d})⍣(</11 25≥m d←2↑1↓⎕TS)?⍨25

Try it online!

?⍨25 random selection without replacement of 25 out of 1…25

⍣() if:

⎕TS date in [Y,M,D,h,m,s,t] format

1↓ drop one element; [M,D,h,m,s,t]

2↑ take two elements; [M,D]

m d← distribute to variables m=M and d=D (for month-of-year and day-of-month numbers)

11 25≥ are [11,25] greater than or equal to these (gives [0,1] during advent, [1,0] or [1,1] before, and [0,0] after)

</ if the first number is less than the second…

() then apply the following function:

  @{} at elements where the following condition is true:

  ⍵=d the elements are equal to d (today's day-of-month number)

  '[@]'¨ replace each with the constant "[@]"

  @{} at elements where the following condition is true:

  ⍵∊⍳d the elements are members of 1…d (today's day-of-month number)

  '[ ]'¨ replace each with the constant "[ ]"

5 5⍴ reshape into a five-by-five matrix

\$\endgroup\$
  • \$\begingroup\$ I started learning APL but I'm still at early stages (chapter 10) , I suppose it checks for months as </11 25≥m , can I ask you why you used p in the input? Is it something like an anonymous function? \$\endgroup\$ – AZTECCO Dec 15 '19 at 1:19
  • 1
    \$\begingroup\$ @AZTECCO p is just the name of the program. TIO's Input corresponds to APL's session (REPL), so p is there to run the program. \$\endgroup\$ – Adám Dec 15 '19 at 1:20
  • 1
    \$\begingroup\$ @AZTECCO I'm always happy to assist with your APL learning over in the APL Orchard. \$\endgroup\$ – Adám Dec 15 '19 at 1:27
  • 1
    \$\begingroup\$ @AZTECCO And regarding the checking for months, yes indeed. I've added a full explanation now. Let me know if you want any further explanation of the algorithm. \$\endgroup\$ – Adám Dec 15 '19 at 1:28
7
\$\begingroup\$

Python 2, 181 189 181 179 Bytes

Original Code

Since I can't comment on other answers yet, this is a slight improvement on ElPedro's previous answer. Try it online! I changed ElPedro's 5th line to save an additional 9 bytes.

i=__import__
t=i("datetime").date.today();n=t.day
c=[('['+(' ','@')[d==n]+']',`d`)[t.month<12or d>n]for d in i("random").sample(range(1,26),25)]
while c:print' '.join(c[:5]);c=c[5:]

Correction #1

Gained 6 bytes in order to fix an error where windows remain open after December 25th until the new year. Try it online!
See corrected version of line 3 below:

c=[('['+(' ','@')[d==n]+']',`d`)[t.month<12or n>25or d>n]for d in i("random").sample(range(1,26),25)]

Correction #2

As per AZTECCO's suggestions, I'm back at 181 bytes :) Try it online!

c=[('['+' @'[d==n]+']',`d`)[t.month-12|25-n|n-d<0]for d in i("random").sample(range(1,26),25)]

Final

With streamlined import statements (Thanks to Reinstate Monica)

import random,datetime as D
t=D.date.today();n=t.day
c=[('['+' @'[d==n]+']',`d`)[t.month-12|25-n|n-d<0]for d in random.sample(range(1,26),25)]
while c:print' '.join(c[:5]);c=c[5:]
\$\endgroup\$
  • \$\begingroup\$ Hi and welcome to the site! Thanks for crediting me with the original answer. There seems to be a small problem with dates after 25 Dec Try it online!. If you fix that you will certainly get an upvote from me :) \$\endgroup\$ – ElPedro Dec 16 '19 at 19:48
  • \$\begingroup\$ Nice catch! Thanks for pointing that out. The new version should work past Dec 25th now :) \$\endgroup\$ – Grumpy_Boy Dec 16 '19 at 22:15
  • 1
    \$\begingroup\$ Suggestion c=[('['+' @'[d==n]+']',d) \$\endgroup\$ – AZTECCO Dec 16 '19 at 23:52
  • 2
    \$\begingroup\$ [t.month-12|25-n|n-d<0] saves 2 more (if one of the 3 sets some negative bits the expression becomes true) \$\endgroup\$ – AZTECCO Dec 17 '19 at 0:15
  • 2
    \$\begingroup\$ You can get this even shorter in Python 3 \$\endgroup\$ – Reinstate Monica Dec 17 '19 at 16:55
6
\$\begingroup\$

Perl 6, 91 bytes

-1 bytes thanks to nwellnhof

.put for (("[@]"if Date.today~~/12.(0?@(^26))$/),"[ ]"xx+$0,+$0^..25)[*;*].pick(*).rotor(5)

Try it online!

Explanation:

.put for (     ...    )         # Print for each of
          ("[@]"if Date.today~~/12.(0?@(^26))$/), # [@] if today is within the Christmas period
          "[ ]"xx+$0,     # As many [ ]s as the current date
          +$0^..25        # All the number from today to the 25th
                      [*;*]                  # Flatten
                           .pick(*)          # Randomise
                                   .rotor(5) # And split into chunks of 5
\$\endgroup\$
  • \$\begingroup\$ I think you need xx+$0 if the regex doesn't match. But @(^26) saves a byte. \$\endgroup\$ – nwellnhof Dec 15 '19 at 9:37
6
\$\begingroup\$

JavaScript (ES6),  158 ... 150  148 bytes

Saved 2 bytes thanks to @Deckerz

Note: Strictly speaking, this should be marked as ES9 (ECMAScript 2018), in which the output format of Date.prototype.toString has been standardized.

f=(k=25,m,d=Math.random()*25+1|0,t=(new Date+0).match(/c ([01].|2[0-5])|:/)[1])=>k?(m^(m|=1<<d)?(d^t?d<t?'[ ]':d:'[@]')+`
 `[--k%5&&1]:'')+f(k,m):''

Try it online!

How?

(new Date).toString() returns the current date in the following format:

DDD MMM dd yyyy hh:mm:ss TimeZoneString

For instance:

Sun Dec 15 2019 12:29:35 GMT+0000 (Coordinated Universal Time)

We apply the following regular expression to this string:

/c ([01].|2[0-5])|:/

It attempts to match a day of December less than \$26\$ (it's the only month whose 3-letter abbreviation ends in "c"). If not found, we force the first : separator to be matched instead, in order to prevent match() from returning null.

Therefore, the variable \$t\$ (for 'today') is set to either a valid day of December, or undefined if we're outside the relevant period:

t = (new Date + 0).match(/c ([01].|2[0-5])|:/)[1]

\$f\$ is a recursive function that generates a random day \$d\$ in \$[1-25]\$ at each iteration and draws the corresponding slot in the calendar. It keeps track of drawn days in the bitmask \$m\$.

f = (                     // f is a recursive function taking:
  k = 25,                 //   k = remaining number of days to draw
  m,                      //   m = bitmask of drawn days
  d = Math.random() * 25  //   d = random day in [1-25]
      + 1 | 0,            //
  t = ...                 //   t = current day or undefined (see above)
) =>                      //
  k ?                     // if k is not equal to 0:
    ( m ^ (m |= 1 << d) ? //   if m is modified by setting the d-th bit:
        ( d ^ t ?         //     if d is not equal to t:
            d < t ?       //       if t is defined and d is less than t:
              '[ ]'       //         draw an opened window
            :             //       else:
              d           //         draw a closed window
          :               //     else:
            '[@]'         //       draw today's gift
        ) +               //
        `\n `             //     decrement k; append a linefeed at the end of a row
        [--k % 5 && 1]    //     or a space otherwise
      :                   //   else:
        ''                //     we need to pick another day: leave k unchanged and
                          //     append nothing
    ) + f(k, m)           //   append the result of a recursive call
  :                       // else:
    ''                    //   done: stop recursion
\$\endgroup\$
  • \$\begingroup\$ does it check for months? \$\endgroup\$ – AZTECCO Dec 15 '19 at 1:13
  • \$\begingroup\$ although I found your motivation convincing I'm glad you updated your answer. And it's a very valuable one! \$\endgroup\$ – AZTECCO Dec 15 '19 at 15:22
  • 1
    \$\begingroup\$ @AZTECCO No worries. On second thought, I think it actually makes much more sense to test the month. \$\endgroup\$ – Arnauld Dec 15 '19 at 15:51
  • \$\begingroup\$ Saved you 2 bytes f=(a=25,b,c=0|25*Math.random()+1,d=(new Date+0).match(/c ([01].|2[0-5])|:/)[1])=>a?(b^(b|=1<<c)?(c^d?c<d?"[ ]":c:"[@]")+` `[--a%5&&1]:"")+f(a,b):"" \$\endgroup\$ – Deckerz Dec 16 '19 at 17:18
  • \$\begingroup\$ @Deckerz Good catch. Thanks! \$\endgroup\$ – Arnauld Dec 16 '19 at 19:57
5
\$\begingroup\$

05AB1E, 38 37 32 31 30 bytes

žeD₂‹žf‹P'@ú€…[ÿ]Dg25Ÿ«¦.r5ô»

-6 bytes thanks to @Grimmy.

Try it online or try it online with own specified month / day.

Explanation:

že          # Push the current day
  D         # Duplicate it
   ₂‹       # Check if it's smaller than 26
     žf     # Push the current month
       Â    # Bifurcate it (short for Duplicate & Reverse copy)
        ‹   # And check if this is smaller than the month itself
            # (this will check 1-9 < 1-9 (falsey); 10 < 01 (falsey); 11 < 11 (falsey);
            #  and 12 < 21 (truthy) - so this will only be truthy for 12 / December)
         P  # Take the product of the day and both checks,
            # which result in either the day, or 0 if either check was falsey
'@         '# Push character "@"
  ú         # Pad it with the earlier number amount of spaces
   €        # Map over each of these characters:
    …[ÿ]    #  Push string "[ÿ]", where the `ÿ` is automatically filled with the character
Dg          # Duplicate the list, and pop and push its length
  25Ÿ       # Create a list in the range [length, 25]
     «      # And merge it to the earlier created list
¦           # Then remove the first item (either a "[ ]", or the "[@]" if the checks
            #                             were falsey)
 .r         # Randomly shuffle the list
   5ô       # Split the list into sublists of size 5
     »      # Join each inner list by spaces, and these strings by newlines
            # (after which the result is output implicitly)
\$\endgroup\$
  • 1
    \$\begingroup\$ 32 \$\endgroup\$ – Grimmy Dec 17 '19 at 16:13
  • 1
    \$\begingroup\$ @Grimmy I was about to save the edits of the 33 byte version.. And that one 32-byte version can be golfed further to 31 by using €…[ÿ]. :) \$\endgroup\$ – Kevin Cruijssen Dec 17 '19 at 16:19
  • \$\begingroup\$ Alternative 31 \$\endgroup\$ – Grimmy Dec 17 '19 at 16:34
  • 1
    \$\begingroup\$ 12Q to ‹ for -1. \$\endgroup\$ – Grimmy Dec 18 '19 at 16:46
  • \$\begingroup\$ @Grimmy Oh, very smart, thanks! :) \$\endgroup\$ – Kevin Cruijssen Dec 18 '19 at 17:50
4
\$\begingroup\$

R, 113 103 bytes

x=as.POSIXlt(Sys.time())
a=sample(25)
if(x$mo>10&(y=x$md)<26){a[a<y]="[ ]";a[a==y]="[@]"}
write(a,"",5)

Try it online!

A full program that prints an advert calendar as specified.

\$\endgroup\$
4
\$\begingroup\$

Python 2, 231 222 216 202 197 193 189 bytes

i=__import__
t=i("datetime").date.today()
y=t.day
d=[(((`x`,"[ ]")[x<y],"[@]")[x==y],`x`)[t.month<12or y>25]for x in i("random").sample(range(1,26),25)]
while d:print' '.join(d[:5]);d=d[5:]

Try it online!

-3 with many thanks to @AZTECCO

Examples below use an earlier version of the answer but the formula is the same.

Before December: Try it online!

December after 25th: Try it online!

\$\endgroup\$
3
\$\begingroup\$

Red, 135 bytes

d: now c: collect[repeat n 25[keep n]]if all[d/3 = 12
d/4 < 26][repeat n d/4[c/:n:"[ ]"]c/:n:"[@]"]random c
loop 5[print take/part c 5]

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Jelly, 76 75 bytes

⁶o”@;Ø[jⱮ;@Jị@
“j⁶ṖgqS^OßƭẸ#pÆẸ¥aRŀçĠrṬỤ{½9?ȷ²Yḅ»ḲḢ;Ɱ$ŒV12;Ɱ25¤i+Ɱ25ẊÇs5K€Y

Try it online!

A full program that prints an advent calendar. Longer than most Jelly programs because it has to call __import__('datetime').datetime.today().month and __import__('datetime').datetime.today().day in Python.

If the date and time could be provided as an argument, it could be much shorter:

Jelly, 35 34 bytes

⁶o”@;Ø[jⱮ;@Jị@
12;Ɱ25¤i+Ɱ25ẊÇs5K€Y

Try it online!

\$\endgroup\$
  • 1
    \$\begingroup\$ How would the code look if given month and day as arguments? \$\endgroup\$ – Adám Dec 15 '19 at 16:08
  • 1
    \$\begingroup\$ @Adám I’ve added a version that does this. \$\endgroup\$ – Nick Kennedy Dec 15 '19 at 16:26
3
\$\begingroup\$

Ruby, 112 bytes

x=[*1..25].shuffle;5.times{|c|puts x[c*5,5].map{|w|['[@]','[ ]',w][((d=Time.now).day-(d.month-12)*30)<=>w]}*" "}

Try it online!

\$\endgroup\$
0
\$\begingroup\$

C#, 267 bytes

using System;using System.Linq;class _{static void Main(){var n=DateTime.Now;var t=n.Month==12?n.Day:0;Console.WriteLine(string.Join(" ",Enumerable.Range(1,25).OrderBy(_=>new Random().Next(-9,9)).Select((d,i)=>(i%5==0?"\r\n":"")+(d<t?$"[ ]":d>t?$"{d}":"[@]"))));}}

.Net Fiddle

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.