Statically Typed

because Hindley-Milner rocks

Android: Avoiding Custom View with Resources (part 3) – Adding Listeners


Where did I leave off?  It’s been a while since I first made the last two posts on this topic.  We had constructed a view using xml file resources through a combination of various types of Android resources.  We had made a simple listener which could scroll through the levels of images in a multi-level image.  However, we hadn’t connected how the logic between our views and game engine were supposed to interact.

In Android, much of what is done is done through listeners.  These can be thought of as the “control” part of a model-view-controller (MVC) architecture.  The great thing about these systems is that we’re able to abstract away changes in the underlying system from changes in the UI.  Discussing the use of MVC in Android could consume several blog posts, others have already posted more than enough about it in other contexts.  Given a chance, however, I’ll sneak in a few generalizations to the code  so that we could potentially adapt what was previously created for checkers or any game with 8×8 black and white squares all thanks to MVC.

First Order Generalizations

Let’s suppose for a second we can instantly adapt anything to anything.  What would make our lives easier?  What sorts of things might make sense for a chess game in the context of an Android application?  A basic outline of the various components as thought of in an object-oriented design would look like:

  1. The board should know where each piece is, where each square is and what color background each square has.
  2. The pieces should know where they are, where they could potentially go if nothing was in their way and what team they’re on.
  3. Then game engine should know how to filter all potential moves into all allowable moves, how to initiate multi-piece moves, and how to set up the board.

Interfacing with the board itself:

public interface IBoard{
	public abstract IBoardPiece getPiece(int _column, int _row);
	public abstract void setPiece(IBoardPiece _piece);
	public abstract void setPiece(IBoardPiece _piece, int _column, int _row);
	public abstract Drawable getBackground(int _column, int _row);
	public abstract Square getLocation(BoardSquareView _view);
}

This is as sketched above but with the twist that we’re returning the Drawable instead of tile color.  We’ll need this if we want to set the levels of each BoardSquareView without relying on interfacing with the view itself.

Interfacing with each piece:

public interface IBoardPiece {
	public abstract void draw(Canvas _canvas, View _view);
public abstract List<Square> getPotentialMoves();
	public abstract Team getTeam();
 	public abstract int getRow();
 	public abstract int getColumn();
 	public abstract void move(int _column, int _row);
 	public abstract void setOnMoveListener(OnMoveListener _listener);
 	public abstract void removeOnMoveListener(OnMoveListener _listener);
}

Two things will stand out from this list of method calls: a draw function and the ability to set an “OnMoveListener” which we haven’t yet defined.  The first is clear in what it hopes to accomplish, although the choice of placing drawing logic within an element of a model as opposed to the view is questionable.  (Personally, I’m still as of yet undecided about the “draw” call.  I’d much rather return a Drawable here too so that I could pass that to any View.)  The concept and purpose of the OnMoveListener will become clear later.

Finally there is interfacing with the game engine:

public interface IEngine {
	public abstract List<Square> getValidMoves(int _column, int _row);
	public abstract void movePiece(IBoardPiece _piece, int _toCol, int _toRow);
}

Interfacing with the User

The user of the chess game we’re designing is going to participate using his/her hands.  That is, they’re going to be touching the screen to tell us what they want to have happen.  To that end the people at Google have given us the OnTouchListener interface which provides a means of capturing touch events.  For each square on our board (remember, we broke the board down into an 8×8 matrix of BoardSquareView objects) we’ll need one of these.

In a normal game we’d expect the user to first select a piece, determine which squares it could move onto, and then move that piece onto one of those squares.  In an official game of chess touching a piece necessitates moving that piece.  We’re not going to be so draconian.  Let’s have the touch event first signal that we’ve selected a piece and display what moves it could make.  Then, if a piece has been selected, we’ll have touch events signal where we’d like that piece to move.

Let’s flesh out one way to implement our touch listener:

public class OnSquareTouchListener implements OnTouchListener{
	private final IEngine mEngine;
	private final IBoard mBoard;
	private final Vibrator mVibrator;
	private BoardSquareView mSelected;

	public OnTouchSquareListener(IBoard board, IEngine _engine, Vibrator _vibrator){
		mBoard = board;
		mEngine = _engine;
		mSelected = null;
		mVibrator = _vibrator;
	}

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		if(event.getAction() == MotionEvent.ACTION_DOWN){
			switch(v.getBackground().getLevel()){
			case 0:
				if(mSelected != null){
					illegalMove();
				}
				else{
					selectNew((BoardSquareView)v);
				}
				break;
			case 1:
				movePiece((BoardSquareView)v);
				break;
			case 2:
				unselect();
				break;
			}
			return true;
		}
		return false;
	}
	private void selectNew(BoardSquareView _view){
		final IBoardPiece piece = _view.getPiece();
		if(piece != null){
			mSelected = _view;
			mSelected.getBackground().setLevel(2);
			final List<Square> squares
 				= mEngine.getValidMoves(piece.getColumn(), piece.getRow());
			for(Square move : squares){
				mBoard.getBackground(move.mColumn, move.mRow).setLevel(1);
			}
		}
	}
	private void unselect(){
		mSelected = null;
		for(int i=0; i<8; ++i){
			for(int j=0; j<8; ++j){
				mBoard.getBackground(i, j).setLevel(0);
			}
		}
	}
	private void movePiece(BoardSquareView _view){
		Square square = mBoard.getLocation(_view);
		mEngine.movePiece(mSelected.getPiece(), square.mColumn, square.mRow);
		unselect();
	}
	private void illegalMove(){
		mVibrator.vibrate(10);
	}
}

This design suffers from making a simplification (which is a disguised complication) by adding state to the OnTouchListener, namely the “mSelected” variable.  If we were to attempt to assign every board square it’s own version of this listener it could wind up with multiple “selected” squares but never the “selected” square of the actual square we’d like to move a piece to.  That’s because “mSelected” would almost always be null-valued.  Ideally it should be assigned to the class implementing the IBoard interface (along with methods for doing so) but that wasn’t part of the initial design.  It can (should) be addressed at a later date.

Along with the logic for moving a piece, selecting a piece, and unselecting a piece we’ve also included vibration logic for shaking the phone when there’s an illegal move.  In order to have the phone vibrate, we’ll need to set a permission on the phone within it’s manifest:

<uses-permission android:name="android.permission.VIBRATE" />

right before the application bracket.  Here’s a link to the webpage that described how to do this.  Let’s give credit where credit due.

Interfacing with the Pieces

Within the declaration of the IBoardPiece interface were two methods related to something called an OnMoveListener.  This isn’t something Google gave us, it’s home-brew.  The purpose of this method is to allow a piece on the board to signal that it has moved and thus adjust the corresponding views within the board.  Let’s define it now:

public interface OnMoveListener{
	public abstract void onMove(IBoardPiece _piece, int _newCol, int _newRow);
}

Not terribly exciting, I know.  Neither is OnTouchListener that exciting but we managed to do some interesting things there.  So what should this listener do?  It should remove the piece from the old square and add it to the new square.  Then it should force both views to redraw themselves.

protected class PieceMoveListener implements OnMoveListener{
	private final IBoard mBoard;

	public PieceMoveListener(IBoard _board){
		mBoard = _board;
	}

	@Override
	public void onMove(IBoardPiece _piece, int _newCol, int _newRow) {
		mBoard.setPiece(null, _piece.getColumn(), _piece.getRow());
		mBoard.setPiece(_piece, _newCol, _newRow);
	}
}

Almost makes ya wonder if this should not have been just a basic class without an interface.  Then again, perhaps if we were to create a new game within this platform we might want additional functionality.  That said, this accomplishes the whole “swap” the pieces but it does nothing for the redraw.  That logic belongs within the BoardSquareView itself.  We’ll just modify the setPiece member function:

public void setPiece(IBoardPiece _piece){
	mPiece = _piece;
	postInvalidate();
}

where the postInvalidate function was chosen over the invalidate function.  The reason being that we don’t know if we’re going to create super fancy animations or some such requiring a thread dedicated to drawing the imagery.  PostInvalidate doesn’t assume that the view draws itself on the same thread.  This keeps flexibility to a maximum at the small cost of an indirection under the hood.

What’s it look like?

Not much, we’re missing the entire chess engine.  I’ll do that next time, if I’m not outrageously distracted by some other ideas I’d like to play around with on Android.  Eventually I have to start pumping out Apps, myself.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Information

This entry was posted on February 21, 2011 by in Android, Java.
%d bloggers like this: