Tuesday 12 January 2010

Brian's Brain in JavaScript and Haskell

Brian's Brain is a celluar automaton, similar to the Game of Life, but where each cell can be in one of three states (on/off/dying). As an adventure in the absurd, I created a Brian's Brain simulation using JavaScript for the front end and Haskell for the back end. With web sockets being so simple to use, it seems like if I want to design a UI I may as well just use the web. Plus it seems like a good way to learn two technologies at the same time!

The canvas element gives you a rendering surface that you can access with JavaScript. There's many good tutorials on using the canvas element, I read Creating a Breakout Clone to understand what was going on.

Hopefully (assuming that I've put the script in correctly below), you should see a grid below. Clicking and dragging the mouse across the grid changes the state.




Your browser does not support canvas, but imagine something awesome here instead!






The JavaScript code to do this is available here. The idea is that when the user clicks a button on the JavaScript side, we'll package the grid up as a string, send it across to Haskell, get the data transformed, and send it back again. The first line sent from JavaScript contains the size of the grid; it's assumed to be square because that was easiest! The next line contains a complete dump of all the data in the grid.


if ('WebSocket' in window) {
ws = new WebSocket('ws://localhost:9876/');
ws.onopen = function() {
$('#connectionStatus').text('Connection opened');
}
ws.onclose = function() {
$('#connectionStatus').text('Connection closed');
}
ws.onmessage = function(evt) {
updateGrid(evt.data);
drawGrid();
return true;
}
}

$('#step').bind('click', function() { runStep(ws); });

function runStep(ws) {
var cells = '';
var pos = 0;
for (i=0;i<cellCount;++i) {
for (j=0;j<cellCount;++j) {
cells += grid[i][j];
}
}
ws.send(cellCount + '\n' + cells);
}


The first step on the Haskell side is to make something reusable from the code I had last time for the web element. This means exposing it as a module and separating out the logic for handling requests from server them. The complete code is here but the main changes are to make acceptLoop function accept an additional argument which handles communication back and forth.


module Web (serverListen, sendFrame, readFrame) where

acceptLoop :: Socket -> (Handle -> IO ()) -> IO a
acceptLoop socket f = forever $ do
(h,_,_) <- accept socket
hPutStr h serverHandshake
hSetBuffering h NoBuffering
forkIO (f h)


With this code we can now write a new web sockets server just by writing a small function to handle the sending and receviing of messages.


listenLoop :: Handle -> IO ()
listenLoop h = do
msg <- readFrame h
sendFrame h msg
listenLoop h

main :: IO ()
main = serverListen 9876 listenLoop


In the next post, I'll look at the game logic in Haskell.