Draw A Snowflake

Joe lives at the Bahamas. It is winter. His children are disappointed that there is no snow. Joe needs to make snow for his children. Fortunately, he has a 3-d printer. He plans to make snowflakes with it. Unfortunately he has no idea how a snowflake would look like. In fact, he has never seen a snowflake! Let us help him by creating a program that automatically generates a 2d-image of a snowflake for him.

Input

The diameter of the image(in pixels), the percentage of the image that is actually a snowflake.

Output

An image of a snowflake with the required diameter. It can be saved to a file or displayed to the user.

Specifications

Create a wedge that has a 30 degree angle. Create a Brownian Tree with initial seed at the point of the wedge. Reflect the wedge around the center of the image 12 times to generate the rest of the image. The snowflake has the color White. The background has the color Black.

Scoring

Due to the fact there are different ways to generate a Brownian Tree, the score is 10 * number of upvotes - golf score.

Golf score is defined as the number of bytes in the program with the following bonuses:

-20% Can arbitrarily specify the symmetry of the snowflake.

-50% Can specify the shape of the snowflake. (By being able to specify the ratio of the lengths of the sides of the wedge.)

Highest score wins.

Here is a picture what the shape of the wedge would be with the ratio approximately 2:

Scoreboard:

Martin Buttner: 10 * 14 - 409 = -269

Nimi: 10 * 1 - 733 * .5 = -356.5

Optimizer: 10 * 5 - 648 = -598

The winner is Martin with score -269!

• Related – Martin Ender Dec 18 '14 at 20:05
• I cannot understand why, if we're allegedly helping someone who has never seen a snowflake know what they look like, we're supposed to make them have rotational symmetry of order 4. Are we supposed to be trolling the poor guy? – Peter Taylor Dec 18 '14 at 20:46
• @Conor "The score is 10 * number of upvotes - golfscore." That program would have a score of -300000000. That is very low. – TheNumberOne Dec 19 '14 at 0:05
• 6x60deg wedges! an improvement on what it said at the time of @PeterTaylor 's comment , but actually you need 12x30deg wedges.. 6 for the right hand side of each of the 6 points, and 6 reflected ones for the left hand side of each point. BTW, I don't understand the second bonus – Level River St Dec 19 '14 at 15:20
• @Optimizer Done, should be clearer now. – TheNumberOne Dec 30 '14 at 4:38

Mathematica, 409 bytes

{n,p}=Input[];m=999;Clear@f;_~f~_=0;0~f~0=1;r=RandomInteger;For[i=0,i<m,++i,For[x=m;y=0,f[x+1,y]+f[x-1,y]+f[x,y+1]+f[x,y-1]<1,a=b=-m;While[x+a<0||y+b<0||(y+b)/(x+a)>Tan[Pi/6],a=-r@1;b=r@2-1];x+=a;y+=b];x~f~y=1];Graphics[{White,g=Point/@Join@@{c=Cases[Join@@Table[{i,j}-1,{i,m},{j,m}],{i_,j_}/;i~f~j>0],c.{{1,0},{0,-1}}},Array[Rotate[g,Pi#/3,{0,0}]&,6]},Background->Black,ImageSize->n*p,ImageMargins->n(1-p)/2]


Ungolfed:

{n,p}=Input[];
m = 999;
ClearAll@f;
_~f~_ = 0;
0~f~0 = 1;
r = RandomInteger;
For[i = 0, i < m, ++i,
For[x = m; y = 0,
f[x + 1, y] + f[x - 1, y] + f[x, y + 1] + f[x, y - 1] < 1,
a = b = -m;
While[x + a < 0 || y + b < 0 || (y + b)/(x + a) > Tan[Pi/6],
a = -r@1;
b = r@2 - 1
];
x += a;
y += b
];
x~f~y = 1
];
Graphics[
{White, g =
Point /@
Join @@ {c =
Cases[Join @@ Table[{i, j} - 1, {i, m}, {j, m}], {i_, j_} /;
i~f~j > 0], c.{{1, 0}, {0, -1}}},
Array[Rotate[g, Pi #/3, {0, 0}] &, 6]},
Background -> Black,
ImageSize -> n*p,
ImageMargins -> n (1 - p)/2
]


This expects input the form {n,p} where n is the image size in pixels, and p is the percentage of the image to be covered by the snowflake.

It takes something like half a minute to generate a snowflake with the given parameters. You can speed it up by changing the value of m from 999 to 99, but then the result looks a bit sparse. Likewise, you can crank up the quality by using larger numbers, but then it'll take very long.

I'm forming the Brownian tree on an integer lattice, placing new particles at {999, 0}, and moving the randomly to the left and up or down (not to the right), until they hit the existing particles. I'm also constraining the motion to the wedge between 0 and 30 degrees. Finally, I reflect that wedge on the x-axis, and display it with its 5 rotations.

Here are some results (click for larger version):

And here are two animations of the Brownian tree growing (10 particles per wedge per frame):

• Wow I like... all of them, actually. The results are nice! – Sp3000 Dec 26 '14 at 1:13

JavaScript, ES6, 799 740 695 658 648

I am only counting the two canvas tags and the function f from the snippet below as part of the byte count. Rest of the stuff is for live demo

To watch it in action, just run the snippet below in a latest Firefox giving the size and ratio via the input boxes

Note that you will have to hide the result then show again before a consecutive snowflake

f=(N,P)=>{E.width=E.height=D.width=D.height=N
E.style.background="#000"
C=D.getContext("2d"),F=E.getContext("2d")
C.strokeStyle='#fff'
M=Math,r=M.random,I=0,n=N/2
C.beginPath()
C.rect(n,n,2,2)
C.fill()
B=_=>{x=n*P/100,y=0,w=[]
do{w.push([x,y])
do{X=2*((r()*2)|0)
Y=2*(((r()*3)|0)-1)
}while(x-X<0||y-Y<0||(y-Y)/(x-X)>.577)
x-=X,y-=Y}while(!C.isPointInPath(n+x,n+y))
I++
w=w.slice(-4)
x=w[0]
C.moveTo(x[0]+n,x[1]+n)
w.map(x=>C.lineTo(n+x[0],n+x[1]))
C.stroke()
E.width=E.height=N
for(i=0;i<12;i++){F.translate(n,n)
i||F.rotate(M.PI/6)
i-6?F.rotate(M.PI/3):F.scale(1,-1)
F.translate(-n,-n)
F.drawImage(D,0,0)}
I<(n*n*P*.22/100)&&setTimeout(B,15)}
B()}
<input placeholder="Input N" id=X /><input placeholder="Input percentage" id=Y /><button onclick="f(~~X.value,~~Y.value)">Create snowflake</button><br>
<canvas id=E><canvas id=D>

Here are a few example renders with different size and percentages. The best one is called SkullFlake (first in the list). Click the images to see them in full resolution.

A lot of help and input from Martin and githubphagocyte.

• This doesn't take the percentage of the image that is filled as input. – TheNumberOne Jan 1 '15 at 15:52
• @TheBestOne it takes percentage into account now. Do note that since this is a Brownian tree based snowflake, neither the percentage nor the ratio for wedge lengths can be accurate as a role of randomness is in play. – Optimizer Jan 1 '15 at 23:32
• This qualifies now. – TheNumberOne Jan 2 '15 at 0:05

The program features the „specify the ratio of the lengths of the sides of the wedge“ option, so you have to call it with three command line arguments:

./sf 150 50 40


Argument #1 is the size of the image, #2 the % of pixels in the wedge and #3 the length (in %) of the shorter side of the wedge. The image is saved in a file called „o.png“.

150-50-40:

My program produces snowflakes with cut-off spikes, because new pixels start on the middle axis of the wedge (green dot, see below) and tend to stay there, because they move equally random to the left, up or down. As pixels outside the wedge are discarded, straight lines appear on the boundary of the wedge (green arrow). I was too lazy to try out other paths for the pixels.

150-50-40:

When the wedge is big enough (3rd argument 100) the spikes on the middle axis can grow and then there are 12 of them.

150-40-100:

Few pixels make round shapes (left: 150-5-20; right 150-20-90).

The program:

import System.Environment;import System.Random;import Graphics.GD
d=round;e=fromIntegral;h=concatMap;q=0.2588
j a(x,y)=[(x,y),(d$c*e x-s*e y,d$s*e x+c*e y)] where c=cos$pi/a;s=sin$pi/a
go s f w p@(x,y)((m,n):o)|x<1=go s f w(s,0)o|abs(e$y+n)>q*e x=go s f w p o|elem(x-m,y+n)f&&(v*z-z)*(b-q*z)-(-v*q*z-q*z)*(a-z)<0=p:go s(p:f)w(s,0)o|1<2=go s f w(x-m,y+n)o where z=e s;a=e x;b=e y;v=e w/100 main = do k<-getArgs;g<-getStdGen;let(s:p:w:_)=map read k i<-newImage(2*s,2*s);let t=h(j 3)$h(\(x,y)->[(x,y),(d$0.866*e x+0.5*e y,d$0.5*e x-0.866*e y)])$take(s*d(q*e s)*pdiv100)$go s[(0,0)]w(s,0)\$map(\r->((1+r)mod2,r))(randomRs(-1,1)g)
mapM(\(x,y)->setPixel(x+s,y+s)(rgb 255 255 255)i)((h(j(-3/2))t)++(h(j(3/2))t));savePngFile "o.png" i

• @Optimizer: the spike is on the middle axis of the wedge. The wedge goes up and down 15 degrees to the x-axis. In a *-*-100 image both of it's sides reach to the left border of the image (see the second image for the position of the wedge). There are pixels on approximately half of the sides - the other halves are empty. – nimi Dec 31 '14 at 14:42
• Using this counter your program has length 841 bytes. – TheNumberOne Jan 1 '15 at 15:40
• @TheBestOne: Tabs vs. Spaces when indenting. I've mixed them up when adding additional 4 spaces for code style. I've edited my post and set Tabs, but they still appear as spaces. Does anyone know how to fix it? – nimi Jan 1 '15 at 15:54
• @nimi On the site TheBestOne linked there's a little hash # link which you can click. You can paste your tabbed code there and link it. – Sp3000 Jan 1 '15 at 16:02
• You could probably make a link to the code somewhere. You could use spaces instead of tabs to indent. You could manually get code style by indenting every line 4 spaces. – TheNumberOne Jan 1 '15 at 16:05

Processing 2 - 575 characters

Takes in a file f whose first line is the image size and the second is the flakes radius. Every time a new point is placed it is rotated around the center 12 times. This creates a very similar effect as a rotated wedge, but not exactly the same.

  int d,w,h,k,l,o,p,x,y;
d=Integer.parseInt(n[0]);
h=Integer.parseInt(n[1]);
size(d,d);
w=d/2;
k=l=(int)random(d);
background(0);
o=p=0;
pixels[w*w*2+w]=color(255);
while(true)
{
o=k+(int)random(-2,2);
p=l+(int)random(-2,2);
if(p*d+o>d*d-1 || p*d+o<0 || o<0 || o>d){
k=l=(int)random(d);
}
else
{
if(pixels[p*d+o]==color(255))
{
p=l-w;
o=k-w;
if(o*o+p*p>h*h){break;}
float s,c;
for(int j=0;j<12;j++)
{
s=sin(PI*j/6);
c=cos(PI*j/6);
x=(int)((o*c)-(p*s));
y=(int)(((p*c)+(o*s)));
pixels[(int)(d*y+x+w+(w*d))]=color(255);
}
k=l=(int)random(d);
}
else
{
k=o;
l=p;
}
}
}
updatePixels();


you can get processing here

• This doesn't quite fit the specifications. This would qualify if you reflected the point around the center instead of rotating it. – TheNumberOne Dec 25 '14 at 5:03
• color(255) can become color(-1) to save one byte – Kritixi Lithos Apr 23 '17 at 15:47