A little Game (1)

As a little exercise for using Animations in DukeScript, I’ve created a little sorting Game. The aim is to sort 15 numbered tiles. You probably remember the mechanical version of this game from your childhood. Here’s a description in Wikipedia.

And here’s what we’ll create in this blog post:

Alt 15-puzzle

Positioning the Tiles with CSS

I’ll start with very little styling for a tile:

.tile{
    /*layout*/
    position: absolute;
    height: 120px;
    width: 120px;
    text-align: center;
    /*typography*/
    font-size: 4em;
    /*style*/
    background: red;
}

#tile-0{
    visibility: hidden;
}

Nothing fancy here, tile position is absolute, and the Tile with id ‘tile-0’ is the empty space. We’ll care about a nicer look and responsive design in another blog entry. Now we need to set the absolute positions of the tiles:

.tile.tile-position-1-1 {
    -webkit-transform: translate(0px, 0px); }
.tile.tile-position-1-2 {
    -webkit-transform: translate(0px, 121px); }
.tile.tile-position-1-3 {
    -webkit-transform: translate(0px, 242px); }
.tile.tile-position-1-4 {
    -webkit-transform: translate(0px, 363px); }
.tile.tile-position-2-1 {
    -webkit-transform: translate(121px, 0px); }
.tile.tile-position-2-2 {
    -webkit-transform: translate(121px, 121px); }
.tile.tile-position-2-3 {
    -webkit-transform: translate(121px, 242px); }
.tile.tile-position-2-4 {
    -webkit-transform: translate(121px, 363px); }
.tile.tile-position-3-1 {
    -webkit-transform: translate(242px, 0px); }
.tile.tile-position-3-2 {
    -webkit-transform: translate(242px, 121px); }
.tile.tile-position-3-3 {
    -webkit-transform: translate(242px, 242px); }
.tile.tile-position-3-4 {
    -webkit-transform: translate(242px, 363px); }
.tile.tile-position-4-1 {
    -webkit-transform: translate(363px, 0px); }
.tile.tile-position-4-2 {
    -webkit-transform: translate(363px, 121px); }
.tile.tile-position-4-3 {
    -webkit-transform: translate(363px, 242px); }
.tile.tile-position-4-4 {
    -webkit-transform: translate(363px, 363px); }

I’ve only added ‘-webkit-transform’. If you want to run it in a non-webkit Browser, you should also add “-moz-transform: translate(…px, …px);” and “transform: translate(…px, …px);’”

The View Model

Now we need to create the view model. We need a list of Tile Objects, and for convenience we’ll store the empty tile separately. The tiles themselves have a target position p, which is also the number displayed on the Tile, and the current position. To make it easier to check valid moves in the grid I’ve split the current position into an x ynd y coordinate.

@Model(className = "Game", properties = {
    @Property(name = "empty", type = Tile.class),
    @Property(name = "tiles", type = Tile.class, array = true),
})
final class ViewModel {

    @Model(className = "Tile", properties = {
        @Property(name = "x", type = int.class),
        @Property(name = "y", type = int.class),
        @Property(name = "p", type = int.class)
    })
    final static class TileModel {
    }

}

The main “game logic” is in a single function called move that will be called when someone clicks a Tile:

@Function
    public static void move(Game game, Tile data) {
        if (data == game.getEmpty()) {
            return;
        }
        int x = data.getX();
        int y = data.getY();
        int x1 = game.getEmpty().getX();
        int y1 = game.getEmpty().getY();
        if ((Math.abs(x1 - x) == 1 && Math.abs(y1 - y) == 0)
                || (Math.abs(x1 - x) == 0 && Math.abs(y1 - y) == 1)) {
            data.setX(x1);
            data.setY(y1);
            game.getEmpty().setX(x);
            game.getEmpty().setY(y);
        }
    }

The function first checks if the Tile is valid (not the empty Tile), and then if it is adjacent to the empty Tile. If so, positions are swapped.

Initializing the Game

Now we’ll initialize the Tiles in random order in the main method:

public final class Main {

    private Main() {
    }

    public static void main(String... args) throws Exception {
        BrowserBuilder.newBrowser().
                loadPage("pages/index.html").
                loadClass(Main.class).
                invoke("onPageLoad", args).
                showAndWait();
        System.exit(0);
    }

    public static void onPageLoad() {
        Game game = initGame();
        Models.applyBindings(game, "game");
    }

    private static Game initGame() {
        LinkedList<Integer> positions = new LinkedList<>();
        for (int i = 0; i < 16; i++) {
            positions.add(i);
        }
        Collections.shuffle(positions);
        Tile empty = null;
        ArrayList<Tile> tiles = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                final Integer pos = positions.pop();
                final Tile tile = new Tile(i, j, pos);
                tiles.add(tile);
                if (pos==0)empty = tile;            
            }
        }
        return new Game(empty, tiles.toArray(new Tile[16]));
    }

}

The View

That’s it for the View Model. What’s missing now is the HTML for the View:

<html>
    <head>
        <title>Sort the Tiles</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" href="css/styles.css" />
    </head>
    <body id="body">

        <div id="game" >
            <div data-bind="foreach: tiles">
                <div class="tile" data-bind="text: p,css: 'tile-position-'+ (y()+1) +'-'+(x()+1), attr: { id: 'tile-'+ p() }, click: $parent.move"></div>
            </div>
        </div>

    </body>
</html>

We just iterate over the Tiles with a foreach-loop. Then we use data binding to set the text (text: p), the id (attr: { id: ‘tile-‘+ p() }), the css class of the current position ( css: ‘tile-position-‘+ (y()+1) +’-‘+(x()+1) ) and we bind the click to the move method (click: $parent.move). Now you can run the game and play it.

Adding Animations

But I promised you some animation. Now that part is a bit disappointing, because we only need to add a single line in the CSS for the ‘tile’ class:

.tile{
    /*...*/
    /*animation*/
    <strong>-webkit-transition: all 0.7s ease-out;</strong>
}

That’s it for this time. We’ve created a little game in just a couple of lines of code and we added animations with a single line of CSS. Stay tuned for the next part, when we’ll store the Games state locally on the device and add a Function for resetting the game.

P.s.: Don’t be frustrated if you can’t solve the puzzle. When doing random shuffling like we do, sometimes the puzzle is unsolvable. We’ll fix that also in the next blog.