The Hourglass

This hourglass has 60 "sands of time", and it completely fills each chamber. The clock is 19 characters wide and 13 characters in height. Each chamber has 5 rows of sand and there is a row in the centre that can hold 1 sand. The top row can hold 17 sands, the next one 15 and so on (see below). Sands fall in to the bottom chamber at the rate of one sand per second.

       START                     3 SECONDS LATER               38 SECONDS LATER

███████████████████            ███████████████████            ███████████████████
█.................█   17       █..............   █   14       █                 █    0
██...............██   15       ██...............██   15       ██               ██    0
███.............███   13       ███.............███   13       ███.......      ███    7
█████.........█████   09       █████.........█████   09       █████.........█████   09
███████.....███████   05       ███████.....███████   05       ███████.....███████   05
█████████.█████████   01       █████████.█████████   01       █████████.█████████   01
███████     ███████            ███████  .  ███████            ███████  .  ███████
█████         █████            █████    .    █████            █████    .    █████
███             ███            ███      .      ███            ███....         ███
██               ██            ██               ██            ██...............██
█                 █            █                 █            █.................█
███████████████████            ███████████████████            ███████████████████


The Challenge

Display the hourglass (no numbers or headings are required) after a certain period of time (0 ≤ t ≤ 60).

Coding Rules

1. The hourglass should look exactly as shown here. You may replace the █ character and/or the . character with whatever you like to fit your language (Unicode, ASCII compatibility issues).
2. The input should be a number such as 45 or 7. Display the clock after these many seconds.
3. The output can either be displayed or saved to a file. No extra text or labels as shown above are required - just the hourglass is all we need.
4. If the user enters t > 60, you don't have to handle the error.

Points

1. Shortest code wins.
• I don't know if it's just me, but the hourglasses looks completely different in the editor than in the post itself. That's probably because the █ character doesn't get displayed with the proper width. Jun 8 '15 at 6:53
• Does a grain of sand touch the current layer, then align to the left one second later?
– xnor
Jun 8 '15 at 7:25
• What should be the output for t==59? Jun 8 '15 at 9:07
• Congrats on making the Stack Exchange Hot Network Questions list! Jun 9 '15 at 15:06
• This is not really an answer, as it doesn't fit the specs, but take a look here for a submission in Desmos. (Press the play button and set the speed to 0.15x for ~1 unit/second) Oct 2 '15 at 13:29

JavaScript (ES6), 203 208 233 270 256 characters

Edit Revised using a loop instead of a sequence of calls.

Edit Added top and bottom row that were missing.

A function returning the output. Run the snippet in Firefox to test.

f=w=>[h='█'[R='repeat'](19),...[17,15,13,9,5,1,5,9,13,15,17].map((d,i)=>(t=i>5?-v:v,v-=i<5?d:1-d,e=' '[R](d/2),b='█'[R](10-d/2),b+('.'[R](t<d&&d-t)+e+' .'[i>4&w>i-6&t>=d|0]+e).slice(0,d)+b),v=w),h].join

// Less golfed
F=
w=>[h='█'.repeat(19),
... [17, 15, 13, 9, 5, 1, 5, 9, 13, 15, 17].map(
(d,i) => (
t = i>5 ? -v : v,
v -= i<5 ? d : 1-d,
e = ' '.repeat(d / 2),
b = '█'.repeat(10 - d / 2),
b +
('.'.repeat(t < d && d - t)
+ e + ' .'[i > 4 & w > i-6 & t >= d | 0]
+ e).slice(0,d)
+ b
), v = w
),
h].join('\n')

// TEST

O.innerHTML=f(+I.value)

function tick(d) {
var i=+I.value+d
I.value=i
O.innerHTML=f(i)
}

var int=0;

function auto()
{
function go()
{
var t = I.value;
O.innerHTML=f(++t)
if (t>70)t=0;
I.value = t;
}
if (A.checked && !int)
{
int = setInterval(go, 200);
}
else if (!A.checked && int)
{
clearInterval(int);
int = 0;
}
}
input[type=text] { width: 3em }
<button onclick='tick(-1)'>-</button>
<input type=text id=I value=0 onchange='tick(0)' >
<button onclick='tick(1)'>+</button>
<input type=checkbox id=A onclick='auto()'>Fly time
<pre id=O><pre>

• For my curiosity, why was this downvoted twice? I can find no problem with it. Jun 8 '15 at 10:28
• This answer is awesome! Open it in Firefox, people! Jun 8 '15 at 12:10
• Good answer, but the top and base of the hourglass seems to be missing. Jun 8 '15 at 12:42
• @manatwork not sure (of course) but there was a flaw indeed Jun 8 '15 at 12:57
• Please make a setInterval(), javascript would be great for that: setTimeout( tick, 1000); Jun 8 '15 at 13:32

Python 2, 200

t=input()+1
s=' '*t+'.'*60+' '*70
n=0
d=sum((1<t<56,2<t<48,3<t<36,4<t<22))
for c in'ctrplhdhlprtc':i=ord(c)-99;print[s[n+i:n:-1],[s[180-n-i+d:][:i],'.'][5+d*3>i>0]][n>59].center(i).center(19,'#');n+=i


xnor has made a 197 byte version in the chat.

I would post an explanation, but I've lost track of how it actually works...

Also, here's an animated version with curses:

from curses import*
w=initscr()

for t in range(1,61):
s=' '*t+'.'*60+' '*70
n=0
d=sum((1<t<56,2<t<48,3<t<36,4<t<22))
for i in 0,17,15,13,9,5,1,5,9,13,15,17,0:w.addstr([s[n+i:n:-1],[s[180-n-i+d:][:i],'.'][5+d*3>i>0]][n>59].center(i).center(19,'#')+"\n");n+=i
w.refresh()
w.clear()
napms(999)

endwin()

• kudos, You made me discover that strings have a center() method ! Jun 8 '15 at 15:24

Python 2.7, 362356 347

e,c,x,a,b,n=' .#ab\n';R,r,s,l,T,m=range,str.replace,'',19,[1,2,3,5,7,9],-1
for t in[T,T[:m][::m]]:s+=''.join([n+x*y+c*(l-y*2)+x*y for y in t]);c=b
s=list(s)
for i in R(130,220,20):s[i]=a
for _ in R(input()):s[s.index('.')]=e;i=s.index(a)if a in s else 219-s[::m].index(b);s[i]='.'
for l in(x*l+r(r(''.join(s),a,e),b,e)+n+x*l).split(n):print l[::m]


Output at 38 seconds :

###################
#                 #
##               ##
###.......      ###
#####.........#####
#######.....#######
#########.#########
#######  .  #######
#####    .    #####
###...   .      ###
##...............##
#.................#
###################

• What kind of Python code is that? I would love to learn to code like them. Please guide in right direction. Jun 8 '15 at 12:20
• I am not sure if this is a valid answer. If you look at the example he made for 38seconds, you can see that the lowest dot which is still falling is not in the line where the dots align. (in the 4th line from bottom, at the center) Jun 8 '15 at 13:39
• @RononDex : As explained in the question's comments, the filling order is not so strict - my implementation sticks the grains of sand to the left in a way that is slightly different from the question, but still valid Jun 8 '15 at 15:21

C 544

Here's what I have so far for a C solution.

#include <stdio.h>
int main(int z,char **a){int r,i,y=i=0,v,d,t,m,s=atoi(a[1]),n[13]={0,43,28,15,6,1,0,1,5,13,25,39,0};char H[13][20];while(y<13){int x,b=x=i=0;v=y-6;t=3+abs(v);m=2*abs(v);d=t<m?t:m;d=9-d;if(d==0)d=10;while (b<d){H[y][b]='#';H[y][18-b]='#';b++;}while(x<19-2*b){if(x<=s-n[y])H[y][x+b]=v>0?' ':'.';else H[y][x+b]=v>0?'.':' ';x++;}if(s>58)r=0;else if(s==58)r=1;else if(s==57)r=2;else if(s==56)r=3;else if(s>38)r=4;else if(s>24)r=3;else if(s>12)r=2;else if(s>4)r=1;while(i<r){H[7+i][9]='.';i++;}H[y][19]='\n';y++;}fputs(H,stdout);}


Compiled with the following command:

gcc -w -o hourglass hourglass.c  // I realize I should have cast H as a char *, but since it works this way, I just decided to suppress the error from the compiler instead to save space.


Admittedly, this language has a lot of bulk- that include statement was a bit of a handicap coming right out of the blocks, but I really was just looking for an excuse to practice using C.

I hope you like my solution, and let me know if you see ways to improve.

• There's a lot a ways to improve, making it worse C but a lot shorter. Jun 15 '15 at 10:51
• Hints: for instead of where,?: instead of if else, global int variables don't need int, include not needed. (I like your math) Jun 15 '15 at 13:06
• 317: r,i,y,x,b,d,n[]={0,43,28,15,6,1,0,1,5,13,25,39,0};char H[14][20];main(s,a)char**a;{for(s=atoi(a[1]);y<13;){b=x=i=0;d=abs(y-6);d+=3<d?3:d;d=9-d?9-d:10;r=s>55?59-s:s>38?4:s>24?3:s>12?2:s>4?1:r;for(;b<19;)H[y][b++]=35;for(;x<19-2*d;x++)H[y][x+d]=(x>s-n[y]?" .":". ")[y>6];for(;i<r;)H[7+i++][9]=46;H[y++][b]=10;}puts(H);} (beware, strange invisible chars are added to long comments like this) Jun 15 '15 at 13:08

Matlab, 252 bytes

The idea is constructing a matrix that looks like this:

0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
0  17  16  15  14  13  12  11  10   9   8   7   6   5   4   3   2   1   0
0   0  32  31  30  29  28  27  26  25  24  23  22  21  20  19  18   0   0
0   0   0  45  44  43  42  41  40  39  38  37  36  35  34  33   0   0   0
0   0   0   0   0  54  53  52  51  50  49  48  47  46   0   0   0   0   0
0   0   0   0   0   0   0  59  58  57  56  55   0   0   0   0   0   0   0
0   0   0   0   0   0   0   0   0  60   0   0   0   0   0   0   0   0   0
0   0   0   0   0   0   0 116 117  61 118 119   0   0   0   0   0   0   0
0   0   0   0   0 108 109 110 111  62 112 113 114 115   0   0   0   0   0
0   0   0  96  97  98  99 100 101  63 102 103 104 105 106 107   0   0   0
0   0  82  83  84  85  86  87  88  64  89  90  91  92  93  94  95   0   0
0  66  67  68  69  70  71  72  73  65  74  75  76  77  78  79  80  81   0
0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0


From there it is easy to fill the entries with the strings dependent on n (filling all entries that are greater than n and smaller than n+60 with dots)

function c=f(n);
b=zeros(13,19);
z=[0,17,32,45,54,59];
y=-2:3;
for k=2:6;
d=k+sum(k>4:5);
b(k,d:20-d)=z(k):-1:z(k-1)+1;
b(14-k,d:19-d)=68+(z(k-1):z(k)-2)-k;
end;
b(8:12,11:19)=b(8:12,10:18);
b(7:12,10)=60:65;c=[ones(13,19)*32,''];
c(~b)='¶';c(n<b)=46;c(b>n+60)=32


For n=38 we get this output:

¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶
¶                 ¶
¶¶               ¶¶
¶¶¶.......      ¶¶¶
¶¶¶¶¶.........¶¶¶¶¶
¶¶¶¶¶¶¶.....¶¶¶¶¶¶¶
¶¶¶¶¶¶¶¶¶.¶¶¶¶¶¶¶¶¶
¶¶¶¶¶¶¶  .  ¶¶¶¶¶¶¶
¶¶¶¶¶    .    ¶¶¶¶¶
¶¶¶...   .      ¶¶¶
¶¶...............¶¶
¶.................¶
¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶


Java, 712

Input is taken from the command line. Handles both negative values for the time as well as larger than required.

enum H{;public static void main(String[]r){int x=0,y=0,z=0,l,t=Integer.parseInt(r[0]);String b="",d="█",e=" ",f=".",n="\n",j,k,a="███████████████████"+n;int[]w={17,15,13,9,5},v;int[][]h=new int[10][];for(;x<5;){l=w[x];v=(h[x++]=new int[l--]);l/=2;v[l]=++z;for(y=0;y++<l;){v[l-y]=++z;v[l+y]=++z;}}for(z=0;x>0;){l=w[--x];v=(h[9-x]=new int[l--]);v[l/2]=++z;}for(;x<5;){l=(w[x]-1)/2;v=h[9-x++];for(y=0;y++<l;){v[l-y]=++z;v[l+y]=++z;}}p(a);for(x=0;x<5;x++){l=w[x];j=b;for(y=0;y++*2<19-l;)j+=d;k=b;for(y=0;y<l;)k+=t<h[x][y++]?f:e;p(j+k+j+n);}j="█████████";p(j+f+j+n);for(;x>0;){l=w[--x];j=b;for(y=0;y++*2<19-l;)j+=d;k=b;for(y=0;y<l;)k+=t<h[9-x][y++]?e:f;p(j+k+j+n);}p(a);}static void p(String s){System.out.print(s);}}


output:

time: 0
███████████████████
█.................█
██...............██
███.............███
█████.........█████
███████.....███████
█████████.█████████
███████     ███████
█████         █████
███             ███
██               ██
█                 █
███████████████████

time: 1
███████████████████
█........ ........█
██...............██
███.............███
█████.........█████
███████.....███████
█████████.█████████
███████  .  ███████
█████         █████
███             ███
██               ██
█                 █
███████████████████

time: 9
███████████████████
█....         ....█
██...............██
███.............███
█████.........█████
███████.....███████
█████████.█████████
███████  .  ███████
█████    .    █████
███      .      ███
██       .       ██
█      .....      █
███████████████████

time: 41
███████████████████
█                 █
██               ██
███..         ..███
█████.........█████
███████.....███████
█████████.█████████
███████  .  ███████
█████    .    █████
███   .......   ███
██...............██
█.................█
███████████████████


It fills the sand from the center expanding outwards. I can probably golf it more if I get lazy with how it fills the bottom half and empties the top half. But for now I quite like it.

h=[17,15,13,9,5,1];b=drop 1$reverse h;n#s=[1..n]>>[s];s='.';n =' ';c q=(q#n)++(60-q)#s;f q|q<=5=q#s|3>2=g#s where{g=foldl i 5 (scanl (+) 0 h);i x y=if q>x+y then x-1 else x};e q=j#s++(59-length(k q)-(j))#n where{j=q-length(f q)};l q=c q++k q++(reverse$e q);p _ []=[];p x y=reverse(z++take q x++z):p (drop (head y) x) (tail y)where{q=head y;z=replicate(div (19-q) 2) '|'};k q= (concat.map(\x -> z x ++ "." ++ z x).take (length.f$q)$b)where{z x=(div x 2)#n};m n=mapM_ putStrLn $t ++ p (l n) (h++b) ++ t;t=[19#'|']  Input m 55 Output ||||||||||||||||||| | | || || ||| ||| ||||| ||||| |||||||.... ||||||| |||||||||.||||||||| ||||||| . ||||||| |||||.........||||| |||.............||| ||...............|| |.................| |||||||||||||||||||  Input m 48 Output  ||||||||||||||||||| | | || || ||| ||| |||||...... ||||| |||||||.....||||||| |||||||||.||||||||| ||||||| . ||||||| |||||.. ||||| |||.............||| ||...............|| |.................| |||||||||||||||||||  • Welcome to PPCG! I see you already golfed this quite a bit, but there is still a lot of unnecessary white space left, eg. surrounding ++. Also you may save some bytes by predefining length and reverse. Jan 23 '17 at 23:42 Ruby: 196190186185 184 characters u=[0,17,15,13,9,5].map{|i|(?.*i).center 19,?#}*$/
(?1..$*[0]).map{u[?.]=' '} l=u.reverse 5.times{|i|l[p=i*20+9]==?.&&l[' ']&&(l[p]=?|)&&l[' ']=?.} puts u,?#*9+?.+?#*9,l.tr('. | ',' .')  CW because not conforms exactly to the posted samples as this consumes sand starting from the left. Mostly just a demonstration of String.[]= method. Sample run: bash-4.3$ ruby hg.rb 38
###################
#                 #
##               ##
###      .......###
#####.........#####
#######.....#######
#########.#########
#######  .  #######
#####    .    #####
###      .   ...###
##...............##
#.................#
###################


Ruby: 215 characters

This is generates the exact required output:

u=[0,17,15,13,9,5].map{|i|(?.*i).center 19,?#}*$/ (?1..$*[0]).map{u[?.]=' '}
l=u.reverse
5.times{|i|l[p=i*20+9]==?.&&l[' ']&&(l[p]=?|)&&l[' ']=?.}
puts ([u,?#*9+?.+?#*9,l.tr('. | ',' .')]*$/).split($/).map &:reverse


Sample run:

bash-4.3\$ ruby hg.rb 38
###################
#                 #
##               ##
###.......      ###
#####.........#####
#######.....#######
#########.#########
#######  .  #######
#####    .    #####
###...   .      ###
##...............##
#.................#
###################


C#, 382 410

it might still be possible to reduce it by a few bytes...

class Program{static void Main(){int u=60-22,d=u,i,j,k,l,m;var c=new char[260];var r=new int[]{0,17,15,13,9,5,1,5,9,13,15,17,0,54,45,32,17,0};for(i=0;i<13;){m=0;l=(19-r[i])/2-1;for(j=19;j>=0;){k=i*20+j--;var b=j>=l&&j<r[i]+l;if(i>6&b)c[k-r[i]+m++ +m]=r[i+6]<d&&d-->0||j==8&r[i+6]>d&&d-->0?'.':' ';else c[k]=i<7&b?u-->1?' ':'.':'█';}c[++i*20-1]='\n';}System.Console.WriteLine(c);}}


Fiddler - 38sec

• Cool! How does it take input? Jun 10 '15 at 5:34
• @isaacg, oops, i will fix that later today, i knew it was too short for a c# solution :-) for now change u=60-22 to something else Jun 10 '15 at 12:29
• Why you use int u=60-22 and not just int u=38?? Oct 3 '15 at 8:04