Implementation

For information about the design and functionality of the MIDlet, see section Design.

The Sudokumaster MIDlet consists of a MIDlet main class and a GameCanvas. All game widgets are implemented as subclasses of a custom View class. Each view has a paint method that defines how the view is drawn to the screen. A Layout view groups the other views and the canvas has a reference to the layout to draw all the views. The Sudoku game board is a view and uses a TiledLayer to draw the game board.

For information about the key aspects of implementing the MIDlet, see:

Game engine

The whole game engine runs inside a GameCanvas. While the main thread handles all user events, rendering is done in a separate thread:

timer = new Timer();
timer.schedule(new TimerTask() {
    public void run() {
        render(g);
    }
}, 0, 20);

Updating the value of the elapsed time view is also done in the rendering thread:

timer.scheduleAtFixedRate(new TimerTask() {
    public void run() {
        updateElapsed();
    }
}, 0, 1000);

To keep the interface lag minimal, the render procedure is called every 20 ms, but the screen is redrawn only if there are changes in the views. However, on every call to the render procedure, all the possible animation counters are updated:

private void render(Graphics g) {
    if(layout.needsRendering()) {
        layout.render(g);
        flushGraphics();
    }
    layout.update();
}

The rendering thread is started when the canvas is shown:

protected void showNotify() {
    // ...
    startTimer();
}

The thread is stopped when the canvas is hidden:

protected void hideNotify() {
    stopTimer();
    // ...
}

Reacting to touch and key events

The game can be played using either the device keypad or touch screen. When using the touch screen, a number selection dialog is displayed after tapping a cell on the game board:

Figure: Number selection dialog

Touch (pointer) and key events can be captured simply by overriding the the following Canvas methods:

For example:

protected void pointerPressed(int x, int y) {
    handlePointerEvent(View.POINTER_PRESSED, x, y);
}

Handling different resolutions

All the views are generated when the canvas is shown:

protected void showNotify() {
    if(sudoku == null) generateLayout();
    // ...
}

The positions of the views are also updated when the size of the canvas changes (for example, because of an orientation change):

protected void sizeChanged(int w, int h) {
    if(timer != null) {
        stopTimer();
        updateLayout(w, h);
        startTimer();
    }
}

Bitmap resources are created for two resolutions: 128 x 160 and 240 x 320 pixels. For other resolutions, the game board bitmap is scaled to fit the screen. The resources are stored to folders named small and medium. In the loadImage function screen, the resolution determines which folder is used:

private Image loadImage(String fileName, int w, int h) {
    Image image = null;
    if(isSmallScreen(w, h)) {
        image = ImageLoader.getInstance().loadImage("/small"+fileName);
    }
    if(image == null) {
        image = ImageLoader.getInstance().loadImage("/medium"+fileName);
    }
    return image;
}

Figure: Game title in different resolutions

Saving and loading the state of the game

The state of the game is stored using a RecordStore:

public void saveGameState() {
    if(sudoku == null) return;
    try {
        RecordStore gameState = RecordStore.openRecordStore("GameState", true);
        if(gameState.getNumRecords() == 0) gameState.addRecord(null, 0, 0);
        byte[] data = sudoku.getState();
        gameState.setRecord(1, data, 0, data.length);
    } catch (RecordStoreException e) {
    }
}

public void loadGameState() {
    if(sudoku == null) return;
    try {
        RecordStore gameState = RecordStore.openRecordStore("GameState", true);
        if(gameState.getNumRecords() == 0) sudoku.newGame(SudokuGenerator.newPuzzle());
        else {
            sudoku.setState(gameState.getRecord(1));
            if(sudoku.isComplete()) showVictoryDialog();
        }
    } catch (RecordStoreException e) {
    }
}

The state is stored when the canvas is hidden:

protected void hideNotify() {
    // ...
    if(!main.isClosed()) saveGameState();
}

It is also stored when the MIDlet is destroyed:

public void destroyApp(boolean unconditional) {
    closed = true;
    if(sudokuCanvas != null) sudokuCanvas.saveGameState();
}

When the canvas is shown, the state of the game is restored:

protected void showNotify() {
    // ...
    loadGameState();
    // ...
}