-->

Saturday, July 14, 2012

The first interactive application

In the last Post we wrote the first "Hello, World!" application. We saw how to import javascript functions in UHC and haste.

We now want to do something more game like. Out goal over the next few post will be to write a breackout clone in haskell, running in the browser! But first there are still a few things we need. To explore this we will write a little application displaying the paddle that can be moved with the arrow keys. Here is a preview (you have to click on it so that it gains focus):

Update: This should now also work in firefox.

For this we need to learn how to:

  • Set callbacks
  • Let different callback communicate
  • Draw on the canvas

GameLoop in JavaScript

Before we want to start our game, we have to allow the browser to load the full page and its elements. Otherwise we can not access e.g. the canvas (the drawing area we will use).

The browser tells us, that it is done with loading by invoking the callback window.onLoad. Depending on how we compile with haste, our main will already be set to the window.onLoad (the option --start==asap prevents this), but in UHC we have to set a callback by hand. We will use the --start=asap option in haste so that our main code can be the same for haste and UHC.

As can be read at several places (e. g. here or here) we can not just write an infinite loop for our GameLoop in javascript because it would block the browser. The contents of the canvas will only be updated when our code returns.

So we need a function that is called in intervals. Javascript allows us to set the interval with window.setInterval.

Setting callbacks

We need to set haskell functions as callbacks to be invoked from javascript code.

UHC

In UHC we import a special function which converts haskell functions to callbacks that can be called from javascript (see Improving UHC js).

import UHC.Ptr
foreign import js "wrapper" mkCb :: IO () -> IO (FunPtr (IO ()))

This converts an IO action to a function pointer that can be passed to javascript.

If we want our callback to have different or more arguments, we have to import wrapper with a different signature. Here we create callbacks that we can pass as key event handlers.

data JSKeyEvent
foreign import js "wrapper"
    mkKeyEventCb :: (JSKeyEvent -> IO ()) -> IO (FunPtr (JSKeyEvent -> IO ()))

We now need to import the functions with which we set the callbacks. The interval function is set with "setInterval" while "onLoad" and the key event callbacks can be set with "addEventListener" which must be called on an element of the webpage. This element can be retrieved with "getElementById". We define simplified versions that take care of creating the callback for us.

data Element

foreign import js "document.getElementById(%1)"
    jsGetElementById :: JSString -> IO Element
getElementById = jsGetElementById . toJS

foreign import js "%1.keyCode"
  keyCode :: JSKeyEvent -> IO Int

foreign import js "%1.addEventListener('keydown',%2,true)"
  jsSetOnKeyDown :: Element -> FunPtr (JSKeyEvent -> IO ()) -> IO ()
setOnKeyDown :: String -> (Int -> IO ()) -> IO ()
setOnKeyDown elemName fp = do
  cb <- mkKeyEventCb fp'
  el <- getElementById elemName
  jsSetOnKeyDown el cb
  where
    fp' event = keyCode event >>= fp

foreign import js "%1.addEventListener('keyup',%2,true)"
  jsSetOnKeyUp :: Element -> FunPtr (JSKeyEvent -> IO ()) -> IO ()

setOnKeyUp :: String -> (Int -> IO ()) -> IO ()
setOnKeyUp elemName fp = do
  cb <- mkKeyEventCb fp'
  el <- getElementById elemName
  jsSetOnKeyUp el cb
  where
    fp' event = keyCode event >>= fp

foreign import js "window.addEventListener('load', %1, 'false')"
  jsSetOnLoad :: FunPtr (IO ()) -> IO ()
setOnLoad :: IO () -> IO ()
setOnLoad fp = mkCb fp >>= jsSetOnLoad

foreign import js "setInterval(%1,%2)"
  jsSetInterval :: FunPtr (IO ()) -> Double -> IO ()
setInterval :: Double -> IO () -> IO ()
setInterval time fp = do
  cb <- mkCb fp
  jsSetInterval cb time

Remember that the >>= operator chains monadic actions. "setOnKeyDown" and "setOnKeyUp" set the event listener on an element defined by the given name. They define wrapper functions that extract the keycode and passes it to our callback functions. This is convenient because the keycode is the information we are really interested in.

We will follow the convention, that functions taking javascript specific parameters (such as JSString) will be prefixed by "js" and have corresponding functions without the "js" prefix.

Haste

Update: The way FFI functions have to writting with haste has changed since the blog post has original been written. At that time returning values from javascript to haskell was a little bit more cumbersome. I have updated this blog post to reflect the new way of doing it. I hope I did not forget something in the process. So if you find an error, please comment.

Setting callbacks in haste is a little different.

For every callback function a javascript function has to be created which invokes the special function "A()" with the callback. This function can than be used for the callback. Read the section "Callbacks" of js-externls.txt in the doc subdirectory of the haste github repository.

The arguments for the haskell function are the second argument of "A()" and have to be passed as a list similar to the required return value of javascript function included by the FFI. Again, this is explained in js-externals.txt.

function jsSetInterval(msecs, cb) {
    window.setInterval(function() {A(cb,[0]);}, msecs);
    return;
}

function jsSetOnLoad(cb) {
    window.addEventListener('load', function() {A(cb,[0]);}, false);
    return;
}

For "setOnKeyUp" and "setOnKeyDown" we do not need to define any haskell function, because we can set them using the haste library in haskell. On the haskell side callbacks have to be created with "mkCallback" and have the Type "JSFun a".

foreign import ccall jsSetInterval :: Double -> JSFun (IO ()) -> IO ()
setInterval :: Double -> IO () -> IO ()
setInterval time cb =
  jsSetInterval time (mkCallback $! cb)

foreign import ccall jsSetOnLoad :: JSFun (IO ()) -> IO ()
setOnLoad cb = jsSetOnLoad (mkCallback $! cb)

setOnKeyDown :: String -> (Int -> IO ()) -> IO Bool
setOnKeyDown elementName cb = withElem elementName $ \e -> setCallback e OnKeyDown cb

setOnKeyUp :: String -> (Int -> IO ()) -> IO Bool
setOnKeyUp elementName cb = withElem elementName $ \e -> setCallback e OnKeyUp cb

The "withElem" functions is defined in the haste library and executes an action with a webpage element defined by the provided name.

Letting callbacks communicate

UHC

In a javascript program we would let the onKeyUp, onKeyDown and Interval functions communicate through global variables. In haskell we do not have a mechanism such as global variables (at least non that I am aware of). In a normal situation we do not need it, because the only moment when main exits is when the program ends.

To store global variables we write a few helper functions in javascript:

var allObjects = {}

function jsSaveGlobalObject(name, obj) {
 allObjects[name] = obj;
}

function jsLoadGlobalObject(name) {
 return allObjects[name];
}

And include them from haskell:

foreign import ccall jsSaveGlobalObject :: JSString -> a -> IO ()
foreign import ccall jsLoadGlobalObject :: JSString -> IO a

saveGlobalObject :: String -> a -> IO ()
saveGlobalObject name obj = jsSaveGlobalObject (toJS name) obj

loadGlobalObject :: String -> IO a
loadGlobalObject name = do
  ptr <- jsLoadGlobalObject (toJS name)
  return $ ptr

We can now load the current state with

state <- jsLoadGlobalObject "state" :: IO State

When we enter one of our callback functions and save it with a corresponding call to "saveGlobalObject".

Haste

For haste the mechanism works the same way, only that the javascript helper functions have to format their return values the way haste needs them:

var allObjects = {}

function jsSaveGlobalObject(name, obj) {
 allObjects[name] = obj;
 return;
}

function jsLoadGlobalObject(name) {
 return allObjects[name];
}

To be able to pass arbitrary objects to the FFI, they have to be converted to a pointer via "fromPtr" and "toPtr".

foreign import ccall jsSaveGlobalObject :: JSString -> Ptr a -> IO ()
foreign import ccall jsLoadGlobalObject :: JSString -> IO (Ptr a)

saveGlobalObject :: String -> a -> IO ()
saveGlobalObject name obj = jsSaveGlobalObject (toJSStr name) (toPtr obj)

loadGlobalObject :: String -> IO a
loadGlobalObject name = do
  ptr <- jsLoadGlobalObject (toJSStr name)
  return $ fromPtr ptr

Drawing on the canvas

Example code for drawing on the canvas in javascript looks like this:

context = document.getElementById("canvas").getContext("2d");
context.clearRect(0.0, 0.0, context.canvas.width, context.canvas.height)
context.setFillColor("green");
context.fillRect(10.0,10.0,100.0,100.0);

Basically all we have to do is import these functions via the FFI.

UHC

Getting the context needs several steps:

  • Get the canvas via getElementById
  • Get the context via getContext

So this is what we do:

data Context2D
foreign import js "%1.getContext('2d')"
    getContext2dFromCanvas :: Element -> IO Context2D

getContext2d :: String -> IO Context2D
getContext2d canvasName = do
  c <- getElementById canvasName
  getContext2dFromCanvas c

Importing the rest of the functions is straight forward:

foreign import js "%1.fillRect(%*)"
  fillRect :: Context2D -> Double -> Double -> Double -> Double -> IO ()
foreign import js "jsSetFillColor(%*)"
  jsSetFillColor :: Context2D -> JSString -> IO ()
setFillColor ctx = jsSetFillColor ctx . toJS
foreign import js "%1.clearRect(%2, %3, %4, %5)"
  clearRect :: Context2D -> Double -> Double -> Double -> Double -> IO ()

foreign import js "%1.canvas.width" canvasWidth :: Context2D -> IO Double
foreign import js "%1.canvas.height" canvasHeight :: Context2D -> IO Double
clear :: Context2D -> IO ()
clear ctx = do
  w <- canvasWidth ctx
  h <- canvasHeight ctx
  clearRect ctx 0.0 0.0 w h

We have defined "clear" for convenience. It clears the whole canvas.

** Note: ** For some reason, "context.setFillColor" does not work on firefox. Therefor a helper function is defined setting the color via fillStyle.

function jsSetFillColor(context, color) {
    context.fillStyle = color;
}

Haste

Again, for haste we have to write javascript functions with the correct return type:

function jsGetContext2d(canvas) {
 return canvas.getContext("2d");
}

function jsFillRect(context, x, y, width, height) {
 context.fillRect(x,y,width,height);
 return;
}

function jsSetFillColor(context, color) {
 context.fillStyle = color;
 return;
}

function jsClear(context) {
 context.clearRect(0.0, 0.0, context.canvas.width, context.canvas.height);
 return;
}

And here the haskell part:

import Haste.Prim
import Haste.DOM
newtype Context2D = Context2D JSAny

foreign import ccall "jsGetContext2D"
  jsGetContext2d :: Elem -> IO Context2D
getContext2d name = withElem name getContext2D

foreign import ccall "jsFillRect"
  fillRect :: Context2D -> Double -> Double -> Double -> Double -> IO ()
foreign import ccall jsSetFillColor :: Context2D -> JSString -> IO ()
setFillColor ctx = jsSetFillColor ctx . toJSStr
foreign import ccall "jsClear"
  clear :: Context2D -> IO ()

Again we use "withElem" function from Haskell.DOM which executes an action with the element specified by the provided name.

Putting it all together

All imported functions are named, so that the main code is the same for UHC and haste. The folloging code should explain itself through the comments:

module Main where

import JavaScript

canvasName = "canvas1"

playerY = 380.0
playerWidth = 60.0
playerHeight = 20.0
playerSpeed = 3.0
playerColor = "green"

data State = State {x :: Double}
initState = State 300.0

main = setOnLoad initilize

initilize :: IO ()
initilize = do
  saveGlobalObject "state" initState
  setInterval 30.0 update
  setOnKeyDown canvasName onKeyDown
  return ()

onKeyDown :: Int -> IO ()
onKeyDown code = do
  s <-  loadGlobalObject "state" :: IO State
  let s' = case code of
         39 ->  s {x = (x s) + playerSpeed}
         37 ->  s {x = (x s) - playerSpeed} 
         _  ->  s
  saveGlobalObject "state" s'

update :: IO ()
update = do
  s <-  loadGlobalObject "state" :: IO State
  ctx <- getContext2d canvasName
  clear    ctx
  setFillColor ctx playerColor
  fillRect ctx (x s) playerY playerWidth playerHeight

To compile with UHC you need JavaScript.hs and helpers.js. Than run:

uhc -tjs Main.hs

Edit the resulting HTML page and add

<script type="text/javascript" src="helpers.js"></script>

into the head and

<canvas id="canvas1" width=600 height=500 tabindex="0"></canvas>

to the body.

For haste you need this JavaScript.hs und helpers.js and compile it with:

hastec --with-js=helpers.js Main.hs --start=asap

The "--start=asap" parameter is necessary because we set the onLoad function ourself. Than embed it in this HTML page:

<!DOCTYPE html><html><head><title>Main</title>
        <script type="text/javascript" src="Main.js"></script>
    </head>
    <body>
        <canvas id="canvas1" height="400" width="600" tabindex="0">Your browser des not support canvas</canvas>
    </body>
</html>

The running example can be seen at the beginning of this post.

Summary

This has been a rather long post, but we have learned a lot. With the tools at hand we can now update out game with an callback function we set via "setInterval" and receive keyboard events. The next post will be another step towards our goal of a breakout clone, we will implement the ball and let it bounce on the paddle and the walls.

Creative Commons License
Writing JavaScript games in Haskell by Nathan Hüsken is licensed under a Creative Commons Attribution 3.0 Germany License.

Tuesday, July 10, 2012

Hello, World

The first thing we need on our journey is a compiler that translates out haskell to javascript. On this page: The JavaScript Problem you can find a list of compilers that are able to do that. I will focus on UHC and haste. GHCJS also seems promising, and I might try it later.

So we want to write a little hello world program. The equivalent in javascript would be

document.write("Hello, World!");

which replaces the contents of the document with "Hello, World!". Let us get started.

UHC

The Utrecht Haskell Compiler has a backend allowing to compile haskell to javascript. This page links all the information about the JavaScript backend: UHC-JS

Installing UHC

First we have to install UHC. You can find the code for UHC here UHC GitHub. The build instructions can be found in the EHC sub directory.

But first some dependencies are needed. On ubuntu linux I install them with apt-get:

apt-get install ghc cabal-install build-essentials libtool uuagc

We also need to install a few haskell packages via cabal:

cabal update
cabal install network uulib syb fgl

To install UHC, first clone the repository and change into the EHC directory. Then build and install UHC.

git clone git://github.com/UU-ComputerScience/uhc.git
cd uhc/EHC
./configure
make
sudo make install

In blogs it is common to suggesting getting a cup of coffee at a moment like this, because the make command may take a while. So get a cup of coffee!

If everthing worked out, UHC should be installed and you can compile to javascript via

uhc -tjs Main.hs

(Or however your haskell file is called).

Hello World with UHC

We need to do two things:

  • Get the document.
  • Call write on the document with "Hello, World!" as parameter.

The FFI (ForeignFunctionInterface) of the js backend of UHC is described here Improving UHC js For our purposes, it works like this:

foreign import js "jscommand" haskellName :: Type

Where "jscommand" is the command in javascript, "haskellName" the name of the haskell function we want to define and "Type" the type of the haskell function. "jscommand" may contain "%N" where N is a number refering to the N-th parameter of the haskell function.

So to get the document we first define a type for it and then import a corresponding javascript command

data Document
foreign import js "document" getDocument :: IO Document

To get the document we just have to call "document" in javascript. This returns us the document in the IO monad. The document is in the IO monad because we a calling a foreign function which might have side effects.

Now we want to call "write" on a document.

foreign import js "%1.write(%2)" write :: Document -> JSString -> IO ()

Because the "%1" is in front of the ".write", the first argument to haskell function "write" (which is the document) is the object write is called on (this can all be found in Improving UHC js.

Note that the second argument is of type JSString and not of String. This is because a string in haskell is not the same as a string in javascript. We have to convert a haskell string to a javascript string

type JSString = PackedString
foreign import prim "primStringToPackedString" toJS :: String -> JSString

Now we are ready to write our hello world:

type JSString = PackedString
foreign import prim "primStringToPackedString" toJS :: String -> JSString

data Document
foreign import js "document" getDocument :: IO Document
foreign import js "%1.write(%2)" write :: Document -> JSString -> IO ()

main = do
  doc <- getDocument
  write doc $ toJS "Hello, World!"

Haste

Installing haste

Haste can be found here: Haste GitHub. Instructions for installation can also be found on that page (just read the Building section). It requires installation of fursuit, which can be found here: Fursuit GitHub.

Hello World with haste

Again, we need a way to import a function to get the document and to write into the contents of the document. Information on how to import javascript functions can be found in the doc subdirectory of the haste repository.

Update: The way FFI functions have to writting with haste has changed since the blog post has original been written. At that time returning values from javascript to haskell was a little bit more cumbersome. I have updated this blog post to reflect the new way of doing it. I hope I did not forget something in the process. So if you find an error, please comment.

Haste is not as flexible as UHC when importing JavaScript functions. It does not allow placing the parameters of the haskell function in the javascript code with "%N". It also does not allow the custom type "Document" to be used as a parameter or return type. Instead "JSAny" must be used.

So we create a file "helpers.hs" with out Javascript helper functions:

function objWrite(obj, text) {
    obj.write(text); // Call the method
    return; // Return ()
}

function getDocument() {
    return document;
}

This now allows us to write hello world in Haste:

import Haste
import Haste.Prim

type Document = JSAny

foreign import ccall "objWrite" write :: Document -> JSString -> IO ()
foreign import ccall "getDocument" getDocument :: IO Document

main = do
  doc <- getDocument
  write doc $ toJSStr "Hello World!"

Compile this with:

hastec Main.hs --with-js=helpers.js

Now all we need to do is embed this file in a html page:

<!DOCTYPE html><html><head><title>Main</title>
<script type="text/javascript" src="Main.js"></script>
</head>
<body>
</body>
</html>

Open this with a browser of your choice (I only tried chromium) and it should work.

Edit: Trying it out

As suggested in a comment, I have uploaded the compiled javascript file (the haste version because the UHC version has several dependencies) here: Hello, World!

Download it and this html file into the same directory. Than open the html with a browser of your choice (tested on ubuntu linux, chromium).

Edit: Rewrote with pandoc

Creative Commons License
Writing JavaScript games in Haskell by Nathan Hüsken is licensed under a Creative Commons Attribution 3.0 Germany License.

About this blog

This blog will be about my experience with attempting to write javascript games in haskell. So far I have very little experience in javascript and in haskell. From my first attempts in haskell I can already tell that this will not be easy, as haskell has a complete different approach to programming than languages like C++ (with which I have much more experience).

Why JavaScript and why Haskell?

I am not attempting to write huge games, I want to write small games which are fun to play. An javascript enabled browser seems to be the perfect platform. Games running in the browser need no extra installation and are accessible from almost every computer. This way it is convenient for the audience to reach the games.

But why Haskell? A while ago I encountered haskell for the first time and I was fascinated. The mathematical approach, the functional programming and the lazy evaluation intrigue me. I tend to have no big difficulties in picking up new programming languages, but with haskell this is a different sroty. Also I have read already quite a lot about haskell, there is still a lot of concepts I do not yet understand. Or when I understand them in principle I have no Idea how they would be implemented.

Now it seems that these would be reasons to abandon haskell, and focus on another language that is more comfortable to learn (javascript for example). But I like the challenge and I feel like there will be a big benefit from learning haskell. What I have already seen (and understood) from haskell I like.

There is a lot of opinions and explanations of why one should use haskell in the internet. Just google Why Haskell or look here: Why Haskell matters

The intention of the blog

Because I am not an experienced haskell programmer, this blog will not present much of my own ideas (at least not in the beginning). Instead I will document my experience with the attempt to write JavaScript games in haskell.

All of the information (or at least most if it) I found in the internet or by asking people mailing-lists. I will try to always mark the places where I gather my information.

The intent of this blog will be to make it easier for other with the same goal to get started with javascript games written in haskell. It is an attempt of me to give back to the community. I will try to make the posts very tutorial style. Sometimes I will redirect to other pages when I find it unnecessary to repeat the information here that can be found there.

Edit1: Rewrote with pandoc

Creative Commons License
Writing JavaScript games in Haskell by Nathan Hüsken is licensed under a Creative Commons Attribution 3.0 Germany License.