...
 
Commits (4)
......@@ -50,6 +50,7 @@ public class AddPlayerDialog extends DialogFragment {
* Static initializer for {@link AddPlayerDialog}.
*
* @param playerPos Player position, e.g. Player _1_
* @param markTaken The {@link Mark} that has been chosen already
* @return returns instance of {@link AddPlayerDialog}
*/
public static AddPlayerDialog getInstance(int playerPos, Mark markTaken) {
......@@ -68,6 +69,12 @@ public class AddPlayerDialog extends DialogFragment {
return dialog;
}
/**
* Static initializer for {@link AddPlayerDialog}.
*
* @param playerPos Player position, e.g. Player _1_
* @return returns instance of {@link AddPlayerDialog}
*/
public static AddPlayerDialog getInstance(int playerPos) {
return getInstance(playerPos, null);
}
......
package com.example.tictactoe;
import android.util.Log;
import java.util.Arrays;
/**
* Represents the board of the game, a two-dimensional 3x3 grid.
*/
public class Board {
private static final String TAG = Board.class.getSimpleName();
private Mark[][] mCells;
/**
* Constructor, initializing a 3x3 board of empty {@link Mark}s
*/
Board() {
mCells = new Mark[][]{
{Mark.EMPTY, Mark.EMPTY, Mark.EMPTY},
......@@ -16,19 +20,27 @@ public class Board {
};
}
/**
* Places a mark at the provided X & Y coordinates
*
* @param mark {@link Mark} to place
* @param cellX x coordinate on the board
* @param cellY y coordinate on the baord
* @return true, if the space is Empty; false if it's already taken.
*/
boolean markCell(Mark mark, int cellX, int cellY) {
// Check if cell is empty, return false if not
if(mCells[cellY][cellX] != Mark.EMPTY) {
if (mCells[cellY][cellX] != Mark.EMPTY) {
return false;
}
try {
mCells[cellY][cellX] = mark;
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, String.format("Cell {x: %1d, y: %2d} is out of bounds!", cellX, cellY));
System.out.println(String.format("%1s: Cell {x: %2d, y: %3d} is out of bounds!", TAG, cellX, cellY));
}
Log.d(TAG, Arrays.deepToString(mCells));
System.out.println(String.format("%1s: markCell: %2s", TAG, Arrays.deepToString(mCells)));
return true;
}
......
......@@ -11,6 +11,12 @@ import android.util.Log;
public class BoardMapper {
private static final String TAG = BoardMapper.class.getSimpleName();
/**
* Converts cell view to corresponding coordinates in the {@link Board}
*
* @param cellID resource ID of the cell view
* @return Point, containing the X & Y coordinates
*/
static Point viewToPoint(int cellID) {
switch (cellID) {
case R.id.cell_A1:
......@@ -38,6 +44,12 @@ public class BoardMapper {
}
}
/**
* Converts coordinates on the {@link Board} to the corresponding cell view
*
* @param point coordinates on the Board
* @return resource ID of the cell view
*/
static int pointToView(Point point) {
if (point.equals(0, 0)) {
return R.id.cell_A1;
......
package com.example.tictactoe;
import android.util.Log;
import io.reactivex.Observable;
import io.reactivex.subjects.PublishSubject;
import java.util.List;
import static com.example.tictactoe.Scenarios.*;
import static com.example.tictactoe.Scenario.*;
public class Game {
......@@ -28,7 +27,7 @@ public class Game {
}
void selectSquare(int x, int y) {
Log.d(TAG, String.format("selectSquare: {x: %1d, y: %2d}", x, y));
System.out.println(String.format("%1s: selectSquare: {x: %2d, y: %3d}", TAG, x, y));
boolean success = mBoard.markCell(mCurrentPlayer.getMark(), x, y);
// Exit if unsuccessful
......
......@@ -23,6 +23,9 @@ import io.reactivex.disposables.Disposable;
import java.util.ArrayList;
import java.util.List;
/**
* Hosts the UI and interaction for players with the {@link Game}.
*/
public class MainActivity extends AppCompatActivity implements AddPlayerDialog.AddPlayerDialogListener {
private static final String TAG = MainActivity.class.getSimpleName();
......@@ -92,6 +95,9 @@ public class MainActivity extends AppCompatActivity implements AddPlayerDialog.A
.setAction("Yes", v -> newGame()).show();
}
/**
* Resets the game state, prompting for new players
*/
private void newGame() {
// Clear players
mPlayers = new ArrayList<>();
......@@ -102,6 +108,11 @@ public class MainActivity extends AppCompatActivity implements AddPlayerDialog.A
getPlayer(1);
}
/**
* Prompts for player details
*
* @param player position for the player, e.g. Player 1, Player 2
*/
private void getPlayer(int player) {
AddPlayerDialog dialog;
......@@ -115,12 +126,18 @@ public class MainActivity extends AppCompatActivity implements AddPlayerDialog.A
dialog.show(getSupportFragmentManager(), "PlayerDialog");
}
/**
* Begin a new game
*/
private void startGame() {
mGame = new Game(mPlayers);
mGameUpdates = mGame.getGameUpdates().subscribe(this::onNext);
showPlayers();
}
/**
* Presents the player and their color to the UI
*/
private void showPlayers() {
// Get players
Player player1 = mPlayers.get(0);
......@@ -139,6 +156,11 @@ public class MainActivity extends AppCompatActivity implements AddPlayerDialog.A
mPlayerTwo.setTextColor(player2.getMark() == Mark.X ? x : o);
}
/**
* Handles OnClick events for the cells of the board
*
* @param view view that was tapped
*/
@OnClick({R.id.cell_A1, R.id.cell_A2, R.id.cell_A3,
R.id.cell_B1, R.id.cell_B2, R.id.cell_B3,
R.id.cell_C1, R.id.cell_C2, R.id.cell_C3})
......@@ -151,6 +173,11 @@ public class MainActivity extends AppCompatActivity implements AddPlayerDialog.A
}
}
/**
* Receives updates from the game, checking for end {@link Scenario}s and updating the board
*
* @param update the update from the game
*/
private void onNext(Update update) {
checkForGameOver(update);
// Update Board
......@@ -159,6 +186,11 @@ public class MainActivity extends AppCompatActivity implements AddPlayerDialog.A
updateBoardView(move, mark);
}
/**
* Checks for and responds accordingly to game ending {@link Update}s
*
* @param update the update from the game
*/
private void checkForGameOver(Update update) {
// Check for winning move
if (update.isWinner()) {
......@@ -177,12 +209,23 @@ public class MainActivity extends AppCompatActivity implements AddPlayerDialog.A
}
}
/**
* Updates the cells of the board after players have selected a cell
*
* @param point X, Y coordinate on the board
* @param mark player mark to mark the cell with
*/
private void updateBoardView(Point point, Mark mark) {
Log.d(TAG, String.format("updateBoardView: Move: %1s, Mark: %2s", point, mark));
int cellID = BoardMapper.pointToView(point);
((ImageView) findViewById(cellID)).setImageResource(mark.getValue());
}
/**
* Loops until sufficient number of players are added, starting once there are.
*
* @param player the added player
*/
@Override
public void onPlayerAdded(Player player) {
mPlayers.add(player);
......
package com.example.tictactoe;
/**
* Represents the available Marks for Tic Tac Toe
*/
public enum Mark {
X(R.drawable.ic_x),
O(R.drawable.ic_o),
......@@ -7,10 +10,20 @@ public enum Mark {
private final int id;
/**
* Constructor for creating a Mark
*
* @param id ID of the drawable that corresponds to the Mark
*/
Mark(int id) {
this.id = id;
}
/**
* Resource drawable that corresponds to the Mark
*
* @return resource ID
*/
public int getValue() {
return id;
}
......
package com.example.tictactoe;
/**
* Represents a player of the game
*/
public class Player {
private String name;
private Mark mark;
/**
* Constructor for creating a Player
*
* @param name of the player
* @param mark to represent the player's moves, see {@link Mark}
*/
public Player(String name, Mark mark) {
this.name = name;
this.mark = mark;
......
package com.example.tictactoe;
import android.util.Log;
import java.util.Arrays;
public class Scenarios {
private static final String TAG = Scenarios.class.getSimpleName();
/**
* Class of scenarios / outcomes of the game to check for
*/
public class Scenario {
/**
* Check if three in a row match, horizontally
*
* @param cells 2D array of cells
* @param i index for the Y position
* @return true if scenario met, false otherwise
*/
public static boolean threeHorizontal(Mark[][] cells, int i) {
Log.d(TAG, String.format("threeHorizontal: %1s", Arrays.toString(cells)));
return (cells[i][0] == cells[i][1]) && (cells[i][1] == cells[i][2]) // Row matches
&& (cells[i][0] != Mark.EMPTY); // But isn't Empty
}
/**
* Check if three in a row match, vertically
*
* @param cells 2D array of cells
* @param i index for the Y position
* @return true if scenario met, false otherwise
*/
public static boolean threeVertical(Mark[][] cells, int i) {
Log.d(TAG, String.format("threeVertical: %1s", Arrays.toString(cells)));
return (cells[0][i] == cells[1][i]) && (cells[1][i] == cells[2][i]) // Column matches
&& (cells[0][i] != Mark.EMPTY); // But isn't Empty;
}
/**
* Check if three in a row match, diagonally up [ / ]
*
* @param cells 2D array of cells
* @return true if scenario met, false otherwise
*/
public static boolean threeDiagonalUp(Mark[][] cells) {
Log.d(TAG, String.format("threeDiagonalUp: %1s", Arrays.toString(cells)));
return (cells[0][2] == cells[1][1]) && (cells[1][1] == cells[2][0]) // Diagonal matches
&& (cells[0][2] != Mark.EMPTY); // But isn't Empty;
}
/**
* Check if three in a row match, diagonally down [ \ ]
*
* @param cells 2D array of cells
* @return true if scenario met, false otherwise
*/
public static boolean threeDiagonalDown(Mark[][] cells) {
Log.d(TAG, String.format("threeDiagonalDown: %1s", Arrays.toString(cells)));
return (cells[0][0] == cells[1][1]) && (cells[1][1] == cells[2][2]) // Diagonal matches
&& (cells[0][0] != Mark.EMPTY); // But isn't Empty;
}
......
......@@ -2,12 +2,24 @@ package com.example.tictactoe;
import android.graphics.Point;
/**
* Represents updates to the game
*/
class Update {
private Player player;
private Point move;
private boolean isDraw;
private boolean isWinner;
/**
* Constructor for creating an Update
*
* @param player which player this update is about
* @param x x coordinate of their move on the {@link Board}
* @param y y coordinate of their move on the {@link Board}
* @param isDraw return true if the game has ended in a draw
* @param isWinner return true if the game has been won
*/
Update(Player player, int x, int y, boolean isDraw, boolean isWinner) {
this.player = player;
this.move = new Point(x, y);
......
package com.example.tictactoe;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
public class BoardTest {
@Test
public void markCell() {
Mark[][] expected = {
{Mark.X, Mark.EMPTY, Mark.EMPTY},
{Mark.EMPTY, Mark.EMPTY, Mark.O},
{Mark.EMPTY, Mark.EMPTY, Mark.EMPTY},
};
// Mark cells on board
Board board = new Board();
board.markCell(Mark.X, 0, 0);
board.markCell(Mark.O, 2, 1);
assertArrayEquals(expected, board.getCells());
}
@Test
public void getCells() {
Mark[][] expected = {
{Mark.EMPTY, Mark.EMPTY, Mark.EMPTY},
{Mark.EMPTY, Mark.EMPTY, Mark.EMPTY},
{Mark.EMPTY, Mark.EMPTY, Mark.EMPTY},
};
Mark[][] actual = new Board().getCells();
assertArrayEquals(expected, actual);
}
}
\ No newline at end of file
package com.example.tictactoe;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class GameTest {
private Game game;
@Before
public void setUp() throws Exception {
List<Player> players = new ArrayList<>();
players.add(new Player("TestUser", Mark.X));
players.add(new Player("AWildCompetitorAppeared", Mark.O));
game = new Game(players);
}
@Test
public void selectSquare() {
game.selectSquare(1, 0);
game.getGameUpdates().subscribe((update) -> {
// Mark
assertEquals(1, update.getMove().x);
assertEquals(0, update.getMove().y);
// De-false
assertFalse(update.isDraw());
assertFalse(update.isWinner());
});
}
}
\ No newline at end of file