Friday, February 29, 2008

Generate Rogue-like dungeon in Javascirpt

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

Example
I'll explain how to generate a rogue-like dungeon (maze) randomly.

First of all, get a 2-dimensional array, dungeon[40][80].
Then initialize it and show the zero-cleared dungeon.


<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Dungeon</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<script src="./lib/prototype.js" type="text/javascript"></script>
<script type="text/javascript" language="javascript">
// <![CDATA[

var dungeon_width = 80;
var dungeon_height = 40;

dungeon = new Array(dungeon_height);
for (j=0; j < dungeon_height; j++) {
dungeon[j] = new Array(dungeon_width)
for (i=0; i < dungeon_width; i++) {
dungeon[j][i] = '.';
}
}

function main() {
dungeon.each(function(x) {
var elt = document.createElement('code');
elt.appendChild(document.createTextNode(x.join('')));
$('main').appendChild(elt);
$('main').appendChild(document.createElement('br'));
});
}

window.onload = main;
// ]]>
</script>
</head>
<body>
<div id="main"></div>
</body>
</html>


Secondly, divides rectangles recursively to split the dungeon.
Example reload page to make new splitting.


function split(rect) {
if ((rect.x1 - rect.x0 < margin * 2) ||
(rect.y1 - rect.y0 < margin * 2) ||
(random_range(0,5) === 0)) {
  //above is a ending condition.
return [rect];
} else {
if (random_range(0,2) === 0) {
//split vertical
var a = random_range(rect.y0 + margin, rect.y1 - margin);
return [new rectangle(rect.x0, rect.y0, rect.x1, a),
new rectangle(rect.x0, a, rect.x1, rect.y1)].map(split).flatten();
} else {
//split horizonal
var a = random_range(rect.x0 + margin, rect.x1 - margin);
return [new rectangle(rect.x0, rect.y0, a, rect.y1),
new rectangle(a, rect.y0, rect.x1, rect.y1)].map(split).flatten();
}
}
}


Recursively calls "split" divides rectangles more and more. And the last "flatten" makes them to a list of rects.
Make a room in each rects. These rects help to avoid overlapping rooms each other.
I'm going to connects them with corriders like this.


Now we get "Every rooms is always connected each other at least once".
From this, we can avoid a bad patten of below, it doesn't connected.


Finally, makes corrider between the rooms.
To connect rooms divided horizonal, Use a "N-letter" corrider, and for vertical one use "Z-letter" corrider.
Decide whether horizonal or vertical by their coordinate of rectangles.
Example (reloading page will generate a new dungeon.)

function make_corrider(partitions, rooms) {
var connect_list = new Array();

for (var i = 0; i < partitions.length - 1; i++)
connect_list.push([i, i + 1]);

var connect = function(p0,p1,r0,r1) {
if (p0.y1 == p1.y0) {
var a = random_range(r0.x0, r0.x1);
var b = random_range(r1.x0, r1.x1);
fill(new rectangle(a, half(p0.y0 + p0.y1), a, p0.y1));
fill(new rectangle(b, half(p1.y0 + p1.y1), b, p1.y0));
fill(new rectangle(a, p0.y1, b, p1.y0));
} else if (p0.x1 == p1.x0) {
var a = random_range(r0.y0, r0.y1);
var b = random_range(r1.y0, r1.y1);
fill(new rectangle(half(r0.x0 + r0.x1), a, p0.x1, a));
fill(new rectangle(half(r1.x0 + r1.x1), b, p1.x0, b));
fill(new rectangle(p0.x1, a, p1.x0, b));
}
};

connect_list.each(function(x){
connect(partitions[x[0]],partitions[x[1]],rooms[x[0]],rooms[x[1]])});
}




Additionaly, add corriders to give more randomness.


for (var i = 0; i < partitions.length; i++)
for (var j = i + 1; j < partitions.length; j++)
if (random_range(0,5) == 0)
if (partitions[i].x0 == partitions[j].x1 ||
partitions[i].x1 == partitions[j].x0 ||
partitions[i].y0 == partitions[j].y1 ||
partitions[i].y1 == partitions[j].y0)
connect_list.push([i,j]);


Complete.
Example

Minesweeper in Javascript

http://www.geocities.jp/teruakigemma/nikoniko/minesweeper.html


var field;
var closed_field;

function field_onclick(p) {
switch(field[p]) {
case '*':
field.each(function(x,i) {
if (x == '*') {
$('img' + i).src = 'mine.png';
new Effect.Shake('img' + i);
}
});
alert('Game Over');
main();
break;
case 0:
zero_region(p).map(neighbor).flatten().uniq().each(function(x) {
closed_field[x] = null;
$('img' + x).src = 'mine' + field[x] + '.png';
$('' + x).disabled = 'disabled';
});
break;
default:
closed_field[p] = null;
$('img' + p).src = 'mine' + field[p] + '.png';
$('' + p).disabled = 'disabled';
break;
}
if (closed_field.compact().length == 10) {
field.each(function(x,i) {
if (x == '*') {
$('img' + i).src = 'mine.png';
new Effect.Puff('img' + i);
}
});
alert('Congratulation!');
}
}

function zero_region(p) {
var result = [];
var f = function(n) {
var l = [].without.apply(neighbor(n).select(function(x) {return (field[x] == 0)}),result);
if (l.length != 0) {
result = result.concat(l);
l.each(f);
}
}
f(p);
return result;
}

function neighbor(p) {
var x = Math.floor(p / 9);
var y = p % 9;
var f = function(i,j) {
if (i < 0 || i > 8 || j < 0 || j > 8)
return null;
else
return i * 9 + j;
};
return [f(x-1,y-1),f(x,y-1),f(x+1,y-1),
f(x-1,y),f(x,y),f(x+1,y),
f(x-1,y+1),f(x,y+1),f(x+1,y+1)].compact();
}

function make_mine_field() {
field = $R(0,81,true).map(function(x) {return 0});
closed_field = $R(0,81,true).map(function(x) {return true});
var i = 0;
while(i < 10) {
var p = Math.floor(Math.random() * 81);
if (field[p] != '*') {
field[p] = '*';
i++;
}
}
field.each(function(x,i) {
if (x != '*')
field[i] = neighbor(i).select(function(y) {
return (field[y] == '*')}).length});
}

function main() {
make_mine_field();
Builder.dump();
var e = $('main');
while (e.firstChild) e.removeChild(e.firstChild);
field.inGroupsOf(9).each(function(x,i){
x.each(function(y,j) {
var num = i * 9 + j;
e.appendChild(BUTTON({id: num, onclick: 'field_onclick(' + num + ')'},
IMG({id: 'img' + num, src: 'field.png'})))});
e.appendChild(BR())});
}

window.onload = main;

Depth-first search with append-map.

As Haskell demonstrating depth-first search in the List monad(concatMap), we can easily do it in javascript with append-map.

Search the pair (a,b) such that a + b % 2 == 0.
(prototype.js is required.)


Array.prototype.append = function() {
return [].concat.apply([],this);
}

function solve() {
var a = [0,1,2];
var b = [1,2,4];
return a.map(function(x) {
return b.map(function(y) {
if ((x + y) % 2)
return [[x,y]];
else
return [];
}).append()}).append();
}


And now, let's solve the problem "SEND+MORE=MONEY" in javascript. I used "flatten" in this case.


function solve() {
// var digs = [0,1,2,3,4,5,6,7,8,9];
var digs = $A($R(0,9));
var m = 1;
var o = 0;
var s = 9;
return digs.without(m,o,s).map(function(e) {
return digs.without(m,o,s,e).map(function(n) {
return digs.without(m,o,s,e,n).map(function(d) {
return digs.without(m,o,s,e,n,d).map(function(r) {
return digs.without(m,o,s,e,n,d,r).map(function(y) {
if (1000 * s + 100 * e + 10 * n + d +
1000 * m + 100 * o + 10 * r + e ==
10000 * m + 1000 * o + 100 * n + 10 * e + y)
return ''+s+e+n+d+'+'+m+o+r+e+'='+m+o+n+e+y;
else
return [];
})})})})}).flatten();
}

15 puzzle in javascript

http://www.geocities.jp/teruakigemma/nikoniko/puzzle.html
used prototype.js.

var puzzle;
var zeropos;

function swap_puzzle(x,flag) {
var tmp = puzzle[x];
puzzle[x] = puzzle[zeropos];
puzzle[zeropos] = tmp;
if (flag) {
$('cell' + zeropos).src = puzzle[zeropos] + '.png';
$('cell' + x).src = puzzle[x] + '.png';
if ($A($R(0,15)).all(function(x) {return x == puzzle[x]}))
$('main').appendChild(new Element('span').update('nicely done!'));
};
zeropos = x;
}

function shuffle() {
for (var i = 0; i < 5; i++) {
var a = [];
if (Math.floor(zeropos / 4) != 0) a.push(zeropos - 4);
if (Math.floor(zeropos / 4) != 3) a.push(zeropos + 4);
if (zeropos % 4 != 0) a.push(zeropos - 1);
if (zeropos % 4 != 3) a.push(zeropos + 1);
swap_puzzle(a[Math.floor(Math.random() * a.length)],false);
};
}

function click_cell(x) {
if(x == zeropos - 4) swap_puzzle(zeropos - 4, true);
if(x == zeropos + 4) swap_puzzle(zeropos + 4, true);
if(x == zeropos - 1) swap_puzzle(zeropos - 1, true);
if(x == zeropos + 1) swap_puzzle(zeropos + 1, true);
}

function main() {
puzzle = $A($R(0,15));
zeropos = 0;
shuffle();
var e = $('main');
while (e.firstChild) e.removeChild(e.firstChild);
$A($R(0,15)).inGroupsOf(4).each(function(x) {
x.each(function(y) {
var img = new Element('img',{id:'cell' + y,src:puzzle[y] + '.png',width:64,height:64});
img.observe('click',function () {click_cell(y)});
e.appendChild(img)});
e.appendChild(new Element('br'))});
}

window.onload = main;

Take the Arc Challenge in javascript

Using the browser history manager of the YUI library.

  • test
    First of all, just a test of the browser history manager.
  • said-simple
    A simple implementation. I don't get far to abstract the page transitions and state handlings.
  • said
    I tried to abstract them as possible as I can. Please see the following.


  • function q0() {return {x: $F("said")}};
    show(INPUT({type:"text",id:"said"}),BUTTON({id:"button"},"submit"));
    $("button").onclick = cont(q0,function(query) {
    var said = query["x"];
    show(A({id: "a", href:"javascript:void(0)"},"click here"));
    $("a").onclick = cont(null,function(query) {
    show(SPAN("you said " + said))})});

    If Javascript had the continuation, I could write more better one.

    Compute the Groebner Basis in Scheme.

    I attended class of D-module and read "Ideals, Varieties, and Algorithms".
    I wrote a Scheme program to compute the Groebner basis.
    Used Gauche, a scheme implementation.

    (use srfi-1)
    (use srfi-43)
    (use util.combinations)

    (define (poly . l)
    (apply hash-table (cons 'equal? (remove (lambda (x) (zero? (cdr x))) l))))

    (define (order-lex a b)
    (let loop ((a a)
    (b b))
    (cond
    ((null? a) #f)
    ((null? b) #t)
    ((> (car a) (car b)) #t)
    ((< (car a) (car b)) #f)
    (else (loop (cdr a) (cdr b))))))

    (define (order-grlex a b)
    (let ((sum-a (fold + 0 a))
    (sum-b (fold + 0 b)))
    (cond
    ((> sum-a sum-b) #t)
    ((< sum-a sum-b) #f)
    ((= sum-a sum-b) (order-lex a b)))))

    (define (order-grevlex a b)
    (let ((sum-a (fold + 0 a))
    (sum-b (fold + 0 b)))
    (cond
    ((> sum-a sum-b) #t)
    ((< sum-a sum-b) #f)
    ((= sum-a sum-b) (order-lex (reverse b) (reverse a))))))

    (define (multideg order f)
    (fold (lambda (a b) (if (order a b) a b)) '() (hash-table-keys f)))

    (define (LC order f)
    (hash-table-get f (multideg order f)))

    (define (LM order f)
    (cons (multideg order f) 1))

    (define (LT order f)
    (cons (multideg order f) (LC order f)))

    (define (divide-term a b)
    (cons (map - (car a) (car b)) (/ (cdr a) (cdr b))))

    (define (zero-poly? f)
    (every zero? (hash-table-values f)))

    (define (+poly a b)
    (define ht (poly))
    (hash-table-for-each a (lambda (akey avalue)
    (hash-table-put! ht akey avalue)))
    (hash-table-for-each b (lambda (bkey bvalue)
    (hash-table-update! ht bkey (cut + <> bvalue) 0)))
    (hash-table-for-each ht (lambda (key value)
    (when (zero? value) (hash-table-delete! ht key))))
    ht)

    (define (-poly a b)
    (define ht (poly))
    (hash-table-for-each a (lambda (akey avalue)
    (hash-table-put! ht akey avalue)))
    (hash-table-for-each b (lambda (bkey bvalue)
    (hash-table-update! ht bkey (cut - <> bvalue) 0)))
    (hash-table-for-each ht (lambda (key value)
    (when (zero? value) (hash-table-delete! ht key))))
    ht)

    (define (*poly a b)
    (define ht (poly))
    (hash-table-for-each a (lambda (akey avalue)
    (hash-table-for-each b (lambda (bkey bvalue)
    (hash-table-update! ht (map + akey bkey) (cut + <> (* avalue bvalue)) 0)))))
    (hash-table-for-each ht (lambda (key value)
    (when (zero? value) (hash-table-delete! ht key))))
    ht)

    (define (quotient&remainder order f g-list)
    (define quotients (make-vector (length g-list) (poly)))
    (let loop ((f f)
    (intermediate-divident (poly)))
    (if (zero-poly? f)
    (values (vector->list quotients) intermediate-divident)
    (let ((ind (list-index (lambda (x)
    (every >= (multideg order f) (multideg order x)))
    g-list)))
    (if ind
    (let* ((g (list-ref g-list ind))
    (p (poly (divide-term (LT order f) (LT order g)))))
    (vector-set! quotients ind (+poly p (vector-ref quotients ind)))
    (loop (-poly f (*poly p g)) intermediate-divident))
    (loop (-poly f (poly (LT order f))) (+poly (poly (LT order f)) intermediate-divident)))))))

    (define (quotient-poly order f g-list)
    (receive (q r) (quotient&remainder order f g-list)
    q))

    (define (remainder-poly order f g-list)
    (receive (q r) (quotient&remainder order f g-list)
    r))

    (define (S-poly order f g)
    (define term-r (cons (map max (multideg order f) (multideg order g)) 1))
    (-poly (*poly (poly (divide-term term-r (LT order f))) f)
    (*poly (poly (divide-term term-r (LT order g))) g)))

    (define (groebner order f-list)
    (let ((l (remove zero-poly? (map (lambda (x) (remainder-poly order (S-poly order (car x) (cadr x)) f-list))
    (combinations f-list 2)))))
    (if (null? l)
    f-list
    (groebner order (append f-list l)))))


    And now, let's test them.

    Test.

    (define f (poly '((1 2 1) . 4) '((0 0 2) . 4) '((3 0 0) . -5) '((2 0 2) . 7)))
    (multideg order-lex f) == (3 0 0)
    (LC order-lex f) == -5
    (LM order-lex f) == ((3 0 0) . 1)
    (LT order-lex f) == ((3 0 0) . -5)


    Test.

    (S-poly order-grlex (poly '((3 2) . 1)
    '((2 3) . -1)
    '((1 0) . 1))
    (poly '((4 1) . 3)
    '((0 2) . 1)))

    == '(((3 3) . -1) ((2 0) . 1) ((0 3) . -1/3))



    Test.

    (groebner order-grlex
    (list (poly '((3 0) . 1)
    '((1 1) . -2))
    (poly '((2 1) . 1)
    '((0 2) . -2)
    '((1 0) . 1))))

    == '((((3 0) . 1) ((1 1) . -2))
    (((2 1) . 1) ((0 2) . -2) ((1 0) . 1))
    (((2 0) . -1))
    (((1 1) . -2))
    (((0 2) . -2) ((1 0) . 1)))

    A Graph of Heart





    by Dwight Boddorf,American Mathematical Monthly Volume 115 Number 2 (February 2008) page 113



    I generated this animation by programming the Adobe AfterEffect's brush-stroke effects in javascript.