Tuesday, November 20, 2012

Using NULL constants

I think the Null Object pattern is a good approach to not have null checks in the source code, but I do it a little differently from what is shown at Wikipedia.
First thing - I do not use an interface nor a Null class. Second thing - I use a constant to hold the Null object.
class MyClass {
  public static final MyClass NULL = new MyClass();
}

If I add a method to MyClass that changes the object state I change the NULL initialization to use an inner class.
class MyClass {
  public static final MyClass NULL = new MyClass() {
    public void setAttribute(int newValue) {}

  };

  private int attribute;

  public void setAttribute(int newValue) {
    this.attribute = newValue;

  }
}


Tuesday, July 17, 2012

One million downloads

It took five years, but it finally hapenned! July 2012 its the month when my chess board reached ONE MILLION downloads!!
First version went live on July 16, 2007. With it I learned how to create a graphical interface that can adapt to the screen size and wrote my findings at this blog.
Up until now I did now earn a dime with this app, but the pride is big nonetheless!
If you've got a phone that runs Java, install and see what other MILLION people has seen. ;)
http://www.getjar.com/mobile/11226/Chess

Tuesday, June 26, 2012

Single Canvas Theory

When you decide to create a custom UI based on low level graphics you might be tempted to fall into one of the following: have a [Game]Canvas child for each screen or have a single [Game]Canvas child to render all the UI.

I advocate to use a single [Game]Canvas child because we do not how how much memory each [Game]Canvas instance might need. Different JVMs will use more or less memory for [Game]Canvas and Displayable classes.

But it is a bad idea to keep all UI logic in a sigle file. We could easily get above a thousand lines of code.

A simple way to use a single Canvas child and split UI logic in different classes is to use a Paintable class. It will have all event delivery methods from Canvas ([show|hide]Notify, key[Press|Repeat|Releas]ed, pointer[Press|Dragg|Releas]ed and paint) so it can render itself and react to visibility transiction and user input. If we were to use a single GameCanvas instance the approach would be different.

The differences to Canvas are:

  • key[Press|Repeat|Releas]ed methods will receive an extra parameter: action, which is initiated with getGameAction(keyCode).
  • key[Press|Repeat|Releas]ed and pointer[Press|Dragg|Releas]ed methods will return boolean: true if the screen needs to be repainted.
  • from all other Canvas methods only get[Width|Height] are really necessary
The Paintable instance will have a CommandListener attribute to fire specific events to the Canvas: repaint, exit or change screen.

Bellow is the code for the Paintable class:

abstract class Paintable {

  private int width, height;
  private CommandListener listener;

  final void init(CommandListener listener, int w, int h) {
    this.listener = listener;
    this.width = w;
    this.height = h;
  }

  final public int getWidth() {
    return width;
  }

  final public int getHeight() {
    return height;
  }

  final void repaint() {
    listener.commandAction(MainCanvas.CMD_REPAINT, null);
  }

  final void exit() {
    listener.commandAction(MainCanvas.CMD_EXIT, null);
  }

  /**
   * @param id one of SCR constants from MainCanvas
   */
  final void show(int id) {
    Command change = new Command("", Command.SCREEN, id);

    listener.commandAction(change, null);
  }

  boolean keyPressed(int keyCode, int gameAction) {
    return false;
  }

  boolean pointerPressed(int x, int y) {
    return false;
  }

  void showNotify() {
  }

  void hideNotify() {
  }

  abstract void paint (Graphics g);
}
Below is my MainCanvas class:
public class MainCanvas extends Canvas 
  implements CommandListener 
{

  static final Command CMD_REPAINT = new Command("",
    Command.SCREEN, 1);
  static final Command CMD_EXIT = new Command("", 
    Command.EXIT, 1);
  
  static final int SCR_SPLASH = 1;
  static final int SCR_MENU = 2;
  static final int SCR_PLAY = 3;
  
  private Paintable paintable;
  private int paintableId;
  private MIDlet midlet;
  
  public MainCanvas(MIDlet midlet) {
    this.midlet = midlet;
  }
  
  public int getGameAction(int keyCode) {
    int action = 0;

    try {
      action = super.getGameAction(keyCode);
    } catch (Exception e) {}

    return action;
  }
  
  protected void keyPressed(int keyCode) {
    if (paintable.keyPressed(keyCode, getGameAction(keyCode)))
      repaint();
  }
  
  protected void pointerPressed(int x, int y) {
    if (paintable.pointerPressed(x, y))
      repaint();
  }
  
  protected void paint(Graphics g) {
    paintable.paint(g);
  }

  public void commandAction(Command c, Displayable d) {
    if (c == CMD_REPAINT) {
      repaint();
    }
    else if (c == CMD_EXIT) {
      midlet.notifyDestroyed();
    }
    else if (c.getCommandType() == Command.SCREEN) {
      changeScreen(c.getPriority());
    }
  }
  
  public void changeScreen(int id) {
    if (paintableId == id) {
      return;
    }

    Paintable nextPaintable = newPaintableFromId(id);

    if (nextPaintable != null) {
      if (paintable != null) {
        paintable.hideNotify();
      }
      nextPaintable.init(this, getWidth(), getHeight());
      paintable = nextPaintable;
      paintable.showNotify();
      paintableId = id;
      repaint();
    }
  }

  private Paintable newPaintableFromId(int id) {
    Paintable nextPaintable = null;

    switch (id) {
      case SCR_SPLASH:
        nextPaintable = new SplashScreen();
        break;
      case SCR_MENU:
        nextPaintable = new MainMenuScreen();
        break;
      case SCR_PLAY:
        nextPaintable = new BoardScreen();
        break;
    }

    return nextPaintable;
  }
}
Below is the MIDlet that initiates and show MainCanvas:
public class C extends MIDlet {

  private MainCanvas mainCanvas;
  
  public C() {
    mainCanvas = new MainCanvas(this);
    mainCanvas.changeScreen(MainCanvas.SCR_SPLASH);
  }

  protected void startApp() {
    Display.getDisplay(this).setCurrent(mainCanvas);
  }

  protected void pauseApp() {
  }

  protected void destroyApp(boolean arg0) {
  }
}
When SplashScreen needs to show MainMenuScreen it will call show(MainCanvas.SCR_MENU).

Related topics: