#Scala, 211207 Bytes
val b=Array.fill(150)('.')
def s(y:Int,x:Int):Int=={val r=Random.nextInt(6)
val z=y+(if(y>3)-r%2
else if(y<1)r%2
else r/2-1)
b(z*30+x)='$'
z}
(3/:(0 to 28))(s(_,_))
b.mkString("").sliding(30,30).foreach(println)
sample:
...................$$$...$.$$.
.$$$..............$...$.$.$...
$...$$$..$...$$.$$.....$......
.......$$.$.$..$..............
...........$..................
degolfed:
val buf = List.fill(150)('.').toBuffer
def setRowCol (y:Int, x:Int): Int = {
val r = Random.nextInt(6)
val z = y + (
if (y>3)
-(r%2)
else if (y<1)
(r%2)
else
r/2-1
)
buf (z * 30 + x) = '$'
z
}
(3 /: (0 to 28)(setRowCol (_, _))
println
buf.mkString ("").sliding(30,30).foreach(println)
My unique invention - well, I haven't read the other solutions so far, is, to generate a Random (6) which is implicitly two Randoms, (2*3). If away from the border, I use the values of r/2 (0,1,2) and → (-1,0,1) tell me, to go up or down. If at the border, I can avoid the character costly call of another random, and just take the modulo(2), to decide, should I stay or should I go.
Let's see the other solutions. :)