Thursday, November 10, 2011

Project Report - JSSokoban

I have never worked on bigger JavaScript project and I have never worked with 3D. And I never wrote a game. Better late than never, I will create 3D game in JavaScript. The whole project is less ambitious than it sounds, I will create a Sokoban clone.

Sokoban is a turn-based puzzle with simple rules and graphics. The player navigates a little robot through a warehouse. The warehouse contains walls, wooden boxes and destinations.
Click to Play. Works in Firefox, Opera and Chrome. It is somewhat faster in Chrome.

The robot is able to push one box at a time. The goal is to put all boxes into destinations.

The game works in Firefox, Opera and Chrome. It is somewhat faster in Chrome. Click on the above image or here to play it. The source code is on Github too.


I never really worked with JavaScript, so some introductory reading was necessary:

Unit Testing

I will use JsTestDriver to test the prototype. It seems to be simple. JsTestDriver runs from command line so continuous integration is possible. Plus, it has an eclipse plugin so I can see results in JUnit like view.

Set up is described here, it is important to read also comments:

Note: test cases names tend to clash unpredictably. If it does not detect some test methods, try to change test case name.

3D Engine

Three.js framework seems to be a way to go. It can render using canvas, svg and WebGL. Three.js is very light on documentation, but I can learn from their featured demo.

So far, I found some tutorials:

Sokoban Levels

This is supposed to be an exercise in programming, not in level design. I found two sources of levels.
  • Original Sokoban levels are downloadable here.
  • David W. Skinner created sets of fun and small levels called Microban. He allowed me to use them.

Phase 1: Refactor Voxels Demo

Three.js has a Voxels demo that is quite close to what I want to do. I started with that and refactored it until I understood most of it. Then I added some game logic and the first crude version was done.

It paints walls, boxes and robot. The graphic is very simple, robot is a block, wall and box are colored cubes. Keyboard arrows moves the robot and the thing is aware of Sokoban rules. Plus, you can rotate the board by dragging it with a mouse.

If the player turns it upside down, the right arrow moves the robot to the left. It was confusing and Sokoban should not be about spatial imagination anyway, so I want to add 'Up', 'Down', 'Left' and 'Right' texts on the sides. The player would see the text 'Right' on the left side and he would know that he has to press the right arrow to get to the left.

Does it still sound confusing? This is how I imagined it:

It seems to be almost done. There are no tests yet and JavaScript has some gotchas that slowed me down. However, it still went fast enough. Remaining to do list after this phase:
  • mark destinations in the scene,
  • count steps,
  • undo/redo,
  • create nicer robot, walls and boxes,
  • nicel 'level finished' price for player,
  • find better place for initial camera.

Phase 2: Add Textures

The plan was simple: I will put textures over walls and boxes. Three.js has no documentation, but it has demos and issue database on Github. It should take no more than a few minutes. Plus, I wanted to add those 'Up', 'Down', 'Left' and 'Right' texts on the sides.

It took much more than a few minutes. Issue database is less practical for learning than documentation. Three.js is under active development and issues often refer to old API versions. Error messages have not been much helpful (what do you mean by ra[h] is not defined?).

Dynamic nature of JavaScript makes IDE tools pretty weak. I'm used to jump to declaration, show all methods of that object, show all callers/calles. I'm also used to method documentation whenever I hover over a method. Learning unknown code took more time than I'm used to.

The biggest disappointment came when it all finally worked:
  • it was slow, draggable rotation jumped,
  • there was no shading on textures,
  • if all faces of all walls and boxes are the same, the game looks ugly. Especially on big levels.

The point three has a known solution, make multiple textures or find visual that looks good.

The point two has no solution on canvas and I can live with it easily. I would have to use WebGL which is by default turned off in Firefox.

The point one is a problem, I have to optimize somehow. Looking back, Three.js demos have either too many objects with textures, or fast graphic. Plus, their rotate 3D text demo is little jumpy in Firefox too.

Phase 3: Optimize

Get rid of letters and wall textures. If only boxes textures remains, small levels are fast and well again. Not so big levels. The are slow even with no textures at all.

Update the Firefox. Now, rendering is much faster, but all textures disappeared. Download and try Chrome, everything works. Even version with textures and letters is blazing fast in Chrome.

Turns out, that I can afford no more than one 3D text on a small level and has to optimize walls. Big levels are tricky, I will have to optimize as much as possible and then use only small, or if I'm lucky middle sized levels.

My walls have been done in a very ineffective way. I created a small cube for each 1x1 piece of wall. If the wall had 1x6 squares, I created 6 little 1x1x1 cubes. So I changed the code to create one 1x6 block and everything was suddenly much faster.

Small levels rotate perfectly fast in both Firefox and Chrome. Middle sized levels are perfect in Chrome and little jumpy in Firefox. Big levels are little jumpy in Chrome and too slow in Firefox. Internet Explorer renders nothing, but Three.js does not support it anyway.

The robot and bunch of other things are still not done. Plus, it is possible to play with graphics forever. I should probably write unit test, finish functionality and then create levels and optimize.

Phase 4: The Game Itself

All four 'Up', 'Down', 'Left' and 'Right' texts are away again and the navigation is confusing again. I'm used to it, but anyone else would be frustrated by the first level. I'm changing the navigation: right and left arrows will only turn robot, not move it. Much better.

Originally, I through that it would be cool to have an undo feature. Now, I'm starting to see that it is absolutely indispensable. It is very easy to make one too many steps forward or backward and nobody would be willing to redo the level in a casual demo game.

Turns out, that refactoring and changes are much more difficult than I'm used to. It is much easier to make a mistake findable only at run-time. And it sometimes influences unexpected features. Debugging unit tests, not that I would have many of them, is slower too. JavaScript equivalent of call stack would help a lot.

I guess this will get better after I find better tools and acquire more experience. Part of the problem is that I can not code in dynamic language the same way I'm used from statically typed Java. Plus, I'm not fluent in debugging JavaScript unit tests in browser and thus have to use console logs too much.

Phase 5: Graphics

Creating robot shaped object was much easier than I through. I can not afford to give it a texture. However, it looks good without it so I don't care too much.

Up to now, the robot just jumps from place to place and turns instantly. I added slower continuous movement and the game suddenly plays much better. Funny how much those little things helps. Continuous movement was also surprisingly simple.

Finally, player should get some reward for finished level. Alert box with 'Level done.' text feels little disappointing. Even I do not have motivation to continue to the next level. So I added a simple explosion graphic and transparent div over the board. Again, both have been easy and seems to be sufficient.

I would say, that this phase was my favorite one.

Phase 6: The Levels and Final Details

I have to add levels library, add GUI to choose a level, reset it and add a small about box.

Levels library contains mostly Microban levels. I designed only two levels. 'Hello World!' one is more a welcome screen than a level. I'm little bit proud on the second one, 'Invisible Walls'. The level would be extremely simple, if walls would have been visible:

Combo-box with levels selection turned out to be much bigger challenge than expected. Browser differences, of course. Of course, select tag and mouse/keyboard events works differently in Firefox and Chrome. Everything works and than you spend hours with a simple select box. I almost through it will not be possible to make it work in both.

Phase 7: Release

No problem here, Github pages works exactly as promised.

Game Design and Feedback

Almost everybody complained about second level difficulty. Originally, I through about moving it to the end, but then I decided to keep Microban levels as they were designed. The lesson is simple: if it looks to be too difficult, it probably is.

I learned also some other things, although they are probably obvious for anybody with game design experience:
  • End level graphics is a must. Player feels cheated without it.
  • A little randomness in textures makes a huge difference. It may turn ugly cheap looking level to a nice pleasant one.


Three.js was great, JavaScript eclipse less so. Its refactoring support was limited. Code completion was slow. It tended to freeze or throw an exception whenever .js file had errors. I will try Aptana Studio next time.

Refactoring and unit tests turned out to be more challenging than expected. I usually create a lot of unit tests, but I gave up on it in this project. Gain/required effort ratio was too low. Of course, slightly bigger project would be impossible without them.

I guess, I have to find and learn some JavaScript debugger, preferably something with call stack.


mspy said...

The more complex Sokoban levels are, however, out of reach even for the best automated solvers.



Post a Comment