原文:
https://medium.com/@husayn.hakeem/android-by-example-mvvm-data-binding-view-part-4-ecfda0c45db8
This article is part 2/4 from a series or articles about implementing the MVVM design pattern and data binding in a sample Tic-Tac-Toe application. If you’re unfamiliar with the terms data binding or MVVM pattern, refer to part 1 for a quick refresher before continuing along this part.
Note: You’ll find all the code for this project in the following Github repo.
husaynhakeem/TicTacToe-MVVM
TicTacToe-MVVM - Sample android application used to learn the Model View View Model pattern and DataBinding in Android
github.com
M for Model
The model in this application is composed of 3 classes:
Player
Cell
Game
Player
Has 2 attributes, a name and a value.
public class Player {
public String name;
public String value;
public Player(String name, String value) {
this.name = name;
this.value = value;
}
}
Cell
Has a single attribute, a player instance.
public class Cell {
public Player player;
public Cell(Player player) {
this.player = player;
}
public boolean isEmpty() {
return player == null || StringUtility.isNullOrEmpty(player.value);
}
}
Game
Represents an actual Tic-Tac-Toe game, and thus has 2 players, a list (3x3 matrix) of 9 cells. In each round of the game one player makes a move, hence the attribute currentPlayer, and finally a winner.
ps: If you’re not familiar with MutableLiveData, don’t freak out! Just ignore it, consider the winner attribute to be of type Player.
public class Game {
private static final String TAG = Game.class.getSimpleName();
private static final int BOARD_SIZE = 3;
public Player player1;
public Player player2;
public Player currentPlayer = player1;
public Cell[][] cells;
public MutableLiveData winner = new MutableLiveData<>();
public Game() {
cells = new Cell[BOARD_SIZE][BOARD_SIZE];
player1 = new Player(playerOne, "x");
player2 = new Player(playerTwo, "o");
currentPlayer = player1;
}
public void switchPlayer() {
currentPlayer = currentPlayer == player1 ? player2 : player1;
}
}
We should be able to know when a game ends, which is why after each round (i.e move made a either of the players), we need to check if the game has ended. The game ends if the board (cells) have 3 identical horizontal, vertical or diagonal cells, or if there are no more empty cells.
public boolean hasGameEnded() {
if (hasThreeSameHorizontalCells() || hasThreeSameVerticalCells() || hasThreeSameDiagonalCells()) {
winner.setValue(currentPlayer);
return true;
}
if (isBoardFull()) {
winner.setValue(null);
return true;
}
return false;
}
public boolean hasThreeSameHorizontalCells() {
try {
for (int i = 0; i < BOARD_SIZE; i++)
if (areEqual(cells[i][0], cells[i][1], cells[i][2]))
return true;
return false;
} catch (NullPointerException e) {
Log.e(TAG, e.getMessage());
return false;
}
}
public boolean hasThreeSameVerticalCells() {
try {
for (int i = 0; i < BOARD_SIZE; i++)
if (areEqual(cells[0][i], cells[1][i], cells[2][i]))
return true;
return false;
} catch (NullPointerException e) {
Log.e(TAG, e.getMessage());
return false;
}
}
public boolean hasThreeSameDiagonalCells() {
try {
return areEqual(cells[0][0], cells[1][1], cells[2][2]) ||
areEqual(cells[0][2], cells[1][1], cells[2][0]);
} catch (NullPointerException e) {
Log.e(TAG, e.getMessage());
return false;
}
}
public boolean isBoardFull() {
for (Cell[] row : cells)
for (Cell cell : row)
if (cell == null ||cell.isEmpty())
return false;
return true;
}
/**
* 2 cells are equal if:
* - Both are none null
* - Both have non null values
* - both have equal values
*
* @param cells: Cells to check if are equal
* @return
*/
private boolean areEqual(Cell… cells) {
if (cells == null || cells.length == 0)
return false;
for (Cell cell : cells)
if (cell == null || cell.player.value == null || cell.player.value.length() == 0)
return false;
Cell comparisonBase = cells[0];
for (int i = 1; i < cells.length; i++)
if (!comparisonBase.player.value.equals(cells[i].player.value))
return false;
return true;
}
Finally, once a game ends, all attributes related to this game instance should be re-initialized.
public void reset() {
player1 = null;
player2 = null;
currentPlayer = null;
cells = null;
}
As you can see, the models are modular classes representing the data, state and business logic of our Tic-Tac-Toe application. They describe the elements which are the base of our whole app (players, cells), they check whether the game is still ongoing or has ended and know what actions to take when a game is finished, all of the logic is written, now all we need is a component that will orchestrate the whole game… That’s the View Model, which we’ll build in part 3.
ps: The fact that our Model classes are modular make testing them very easy. They don’t interact with the UI, which means Junit tests are enough for testing whether they function correctly.
Thanks for reading this article. Be sure to like and recommend it if you found it helpful.
For more about Java and Android, follow me so that you’ll get notified when I write new posts, or connect with me here on Github and Linkedin.