Introduce CellLocation enum in model and viewmodel layer

This commit is contained in:
Marcel Schwarz 2021-11-03 23:23:13 +01:00
parent 7736fb5101
commit 2294c95517
6 changed files with 121 additions and 78 deletions

View File

@ -1,16 +1,18 @@
package de.icaotix.ultimatetictactoe.model;
import de.icaotix.ultimatetictactoe.model.definitions.CellLocation;
import de.icaotix.ultimatetictactoe.model.definitions.CellState;
import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import java.util.List;
import java.util.Map;
public interface ITicTacToeGame {
void setCellState(int cell, CellState state);
void setCellState(CellLocation cell, CellState state);
CellState[] getCells();
Map<CellLocation, CellState> getCells();
List<Integer> getAvailableFields();
List<CellLocation> getAvailableFields();
boolean isFinished();

View File

@ -1,18 +1,19 @@
package de.icaotix.ultimatetictactoe.model;
import de.icaotix.ultimatetictactoe.model.definitions.CellLocation;
import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import de.icaotix.ultimatetictactoe.model.definitions.Player;
public interface IUltimateTicTacToe {
void initialize();
ITicTacToeGame getSubGame(int id);
ITicTacToeGame getSubGame(CellLocation location);
int getActiveField();
CellLocation getActiveField();
Player getCurrentPlayer();
GameState getGlobalGameState();
void doPlayerMove(int cell);
void doPlayerMove(CellLocation cell);
}

View File

@ -1,42 +1,41 @@
package de.icaotix.ultimatetictactoe.model;
import de.icaotix.ultimatetictactoe.model.definitions.CellLocation;
import de.icaotix.ultimatetictactoe.model.definitions.CellState;
import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
public class TicTacToeGame implements ITicTacToeGame {
public static final Integer[][] winningCombinations = new Integer[][]{
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{0, 3, 6},
{1, 4, 7},
{2, 5, 8},
{0, 4, 8},
{2, 4, 6}
public static final CellLocation[][] winningCombinations = new CellLocation[][]{
{CellLocation.TL, CellLocation.TC, CellLocation.TR},
{CellLocation.ML, CellLocation.MC, CellLocation.MR},
{CellLocation.BL, CellLocation.BC, CellLocation.BR},
{CellLocation.TL, CellLocation.ML, CellLocation.BL},
{CellLocation.TC, CellLocation.MC, CellLocation.BC},
{CellLocation.TR, CellLocation.MR, CellLocation.BR},
{CellLocation.TL, CellLocation.MC, CellLocation.BR},
{CellLocation.TR, CellLocation.MC, CellLocation.BL}
};
private final CellState[] cells;
private final Map<CellLocation, CellState> cells;
private GameState state;
public TicTacToeGame() {
this.cells = new CellState[9];
Arrays.fill(cells, CellState.EMPTY);
this.cells = new HashMap<>();
for (CellLocation value : CellLocation.values) {
this.cells.put(value, CellState.EMPTY);
}
this.state = GameState.RUNNING;
}
// DEBUG
public static void printField(ITicTacToeGame game) {
final var cells = game.getCells();
System.out.println("--------------CURRENT STATE--------------");
for (int i = 0; i < cells.length; i++) {
System.out.print(cells[i] + " ");
if ((i + 1) % 3 == 0) {
for (Map.Entry<CellLocation, CellState> entry : game.getCells().entrySet()) {
System.out.print(entry.getValue() + " ");
if ((entry.getKey().ordinal() + 1) % 3 == 0) {
System.out.println();
}
}
@ -46,25 +45,25 @@ public class TicTacToeGame implements ITicTacToeGame {
}
@Override
public void setCellState(int cell, CellState state) {
public void setCellState(CellLocation cell, CellState state) {
if (this.state != GameState.RUNNING) return;
if (this.cells[cell] == CellState.EMPTY) {
this.cells[cell] = state;
if (this.cells.get(cell) == CellState.EMPTY) {
this.cells.put(cell, state);
updateGameState();
}
}
@Override
public CellState[] getCells() {
public Map<CellLocation, CellState> getCells() {
return cells;
}
@Override
public List<Integer> getAvailableFields() {
final LinkedList<Integer> emptyFields = new LinkedList<>();
for (int i = 0; i < this.cells.length; i++) {
if (this.cells[i] == CellState.EMPTY) {
emptyFields.add(i);
public List<CellLocation> getAvailableFields() {
final LinkedList<CellLocation> emptyFields = new LinkedList<>();
for (Map.Entry<CellLocation, CellState> cellStateEntry : this.cells.entrySet()) {
if (cellStateEntry.getValue() == CellState.EMPTY) {
emptyFields.add(cellStateEntry.getKey());
}
}
return emptyFields;
@ -81,13 +80,13 @@ public class TicTacToeGame implements ITicTacToeGame {
}
private void updateGameState() {
for (Integer[] winningCombination : winningCombinations) {
for (CellLocation[] winningCombination : winningCombinations) {
final var interestingStates = new HashSet<CellState>();
interestingStates.add(this.cells[winningCombination[0]]);
interestingStates.add(this.cells[winningCombination[1]]);
interestingStates.add(this.cells[winningCombination[2]]);
interestingStates.add(this.cells.get(winningCombination[0]));
interestingStates.add(this.cells.get(winningCombination[1]));
interestingStates.add(this.cells.get(winningCombination[2]));
if (interestingStates.size() == 1 && !interestingStates.contains(CellState.EMPTY)) {
switch (this.cells[winningCombination[0]]) {
switch (this.cells.get(winningCombination[0])) {
case O -> this.state = GameState.O_WON;
case X -> this.state = GameState.X_WON;
}
@ -95,7 +94,7 @@ public class TicTacToeGame implements ITicTacToeGame {
}
}
if (Arrays.stream(this.cells).allMatch(cellState -> cellState != CellState.EMPTY)) {
if (!this.cells.containsValue(CellState.EMPTY)) {
this.state = GameState.DRAW;
}
}

View File

@ -1,18 +1,20 @@
package de.icaotix.ultimatetictactoe.model;
import de.icaotix.ultimatetictactoe.model.definitions.CellLocation;
import de.icaotix.ultimatetictactoe.model.definitions.CellState;
import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import de.icaotix.ultimatetictactoe.model.definitions.Player;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class UltimateTicTacToe implements IUltimateTicTacToe {
private ITicTacToeGame[] subGames;
private GameState[] masterGameStates;
private Map<CellLocation, ITicTacToeGame> subGames;
private Map<CellLocation, GameState> masterGameStates;
private Player currentPlayer;
private int activeField;
private CellLocation activeField;
private GameState globalGameState;
public UltimateTicTacToe() {
@ -21,26 +23,26 @@ public class UltimateTicTacToe implements IUltimateTicTacToe {
@Override
public void initialize() {
this.subGames = new ITicTacToeGame[9];
for (int i = 0; i < this.subGames.length; i++) {
this.subGames[i] = new TicTacToeGame();
this.subGames = new HashMap<>();
this.masterGameStates = new HashMap<>();
for (CellLocation value : CellLocation.values) {
this.subGames.put(value, new TicTacToeGame());
this.masterGameStates.put(value, GameState.RUNNING);
}
this.masterGameStates = new GameState[9];
Arrays.fill(this.masterGameStates, GameState.RUNNING);
// X always starts in the center field
this.currentPlayer = Player.X;
this.activeField = 4;
this.activeField = CellLocation.MC;
this.globalGameState = GameState.RUNNING;
}
@Override
public ITicTacToeGame getSubGame(int id) {
return this.subGames[id];
public ITicTacToeGame getSubGame(CellLocation location) {
return this.subGames.get(location);
}
@Override
public int getActiveField() {
public CellLocation getActiveField() {
return activeField;
}
@ -55,13 +57,13 @@ public class UltimateTicTacToe implements IUltimateTicTacToe {
}
@Override
public void doPlayerMove(int cell) {
public void doPlayerMove(CellLocation cell) {
if (!getSubGame(this.activeField).getAvailableFields().contains(cell)) return;
var nextCellState = this.currentPlayer == Player.X ? CellState.X : CellState.O;
getSubGame(this.activeField).setCellState(cell, nextCellState);
this.masterGameStates[this.activeField] = getSubGame(this.activeField).getState();
this.masterGameStates.put(this.activeField, getSubGame(this.activeField).getState());
updateGameState();
@ -73,13 +75,13 @@ public class UltimateTicTacToe implements IUltimateTicTacToe {
}
private void updateGameState() {
for (Integer[] winningCombination : TicTacToeGame.winningCombinations) {
for (CellLocation[] winningCombination : TicTacToeGame.winningCombinations) {
final var interestingStates = new HashSet<GameState>();
interestingStates.add(this.masterGameStates[winningCombination[0]]);
interestingStates.add(this.masterGameStates[winningCombination[1]]);
interestingStates.add(this.masterGameStates[winningCombination[2]]);
interestingStates.add(this.masterGameStates.get(winningCombination[0]));
interestingStates.add(this.masterGameStates.get(winningCombination[1]));
interestingStates.add(this.masterGameStates.get(winningCombination[2]));
if (interestingStates.size() == 1 && !interestingStates.contains(GameState.RUNNING)) {
switch (this.masterGameStates[winningCombination[0]]) {
switch (this.masterGameStates.get(winningCombination[0])) {
case O_WON -> this.globalGameState = GameState.O_WON;
case X_WON -> this.globalGameState = GameState.X_WON;
}
@ -87,16 +89,18 @@ public class UltimateTicTacToe implements IUltimateTicTacToe {
}
}
if (Arrays.stream(this.masterGameStates).allMatch(gameState -> gameState != GameState.RUNNING)) {
if (!this.masterGameStates.containsValue(GameState.RUNNING)) {
this.globalGameState = GameState.DRAW;
}
}
private int findNextAvailableSubGame(int startingIndex) {
while (this.subGames[startingIndex].isFinished()) {
private CellLocation findNextAvailableSubGame(CellLocation startingLocation) {
int startingIndex = startingLocation.ordinal();
while (this.subGames.get(startingLocation).isFinished()) {
startingIndex = (startingIndex + 1) % 9;
startingLocation = CellLocation.values[startingIndex];
}
return startingIndex;
return startingLocation;
}
}

View File

@ -0,0 +1,24 @@
package de.icaotix.ultimatetictactoe.model.definitions;
public enum CellLocation {
TL("Top Left"),
TC("Top Center"),
TR("Top Right"),
ML("Middle Left"),
MC("Middle Center"),
MR("Middle Right"),
BL("Bottom Left"),
BC("Bottom Center"),
BR("Bottom Right");
public static final CellLocation[] values = values();
private final String longName;
CellLocation(String longName) {
this.longName = longName;
}
public String getLongName() {
return longName;
}
}

View File

@ -2,21 +2,27 @@ package de.icaotix.ultimatetictactoe.viewmodel;
import de.icaotix.ultimatetictactoe.model.IUltimateTicTacToe;
import de.icaotix.ultimatetictactoe.model.definitions.CellLocation;
import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class UltimateTicTacToePanelViewModel {
private final TicTacToePanelViewModel[] subGameViewModels;
private final Map<CellLocation, TicTacToePanelViewModel> subGameViewModels;
private final IUltimateTicTacToe ultimateTicTacToe;
private Consumer<String> gameResultCallback;
private Consumer<String> currentPlayerCallback;
public UltimateTicTacToePanelViewModel(IUltimateTicTacToe ultimateTicTacToe, ViewModelFactory viewModelFactory) {
this.subGameViewModels = new TicTacToePanelViewModel[9];
for (int i = 0; i < this.subGameViewModels.length; i++) {
this.subGameViewModels[i] = viewModelFactory.getTicTacToePanelViewModel(this);
this.subGameViewModels = new HashMap<>();
for (CellLocation value : CellLocation.values) {
this.subGameViewModels.put(
value,
viewModelFactory.getTicTacToePanelViewModel(this)
);
}
this.ultimateTicTacToe = ultimateTicTacToe;
}
@ -31,34 +37,41 @@ public class UltimateTicTacToePanelViewModel {
}
}
// Setup next move
final int fieldId = this.ultimateTicTacToe.getActiveField();
final var availableFields = this.ultimateTicTacToe.getSubGame(fieldId).getAvailableFields();
this.subGameViewModels[fieldId].activate(availableFields);
final var cellLocation = this.ultimateTicTacToe.getActiveField();
final var availableFields = this.ultimateTicTacToe
.getSubGame(cellLocation)
.getAvailableFields()
.stream()
.mapToInt(Enum::ordinal)
.boxed()
.toList();
this.subGameViewModels.get(cellLocation).activate(availableFields);
this.currentPlayerCallback.accept("Current player: "
+ this.ultimateTicTacToe.getCurrentPlayer().name());
}
public void onCellClicked(int cell) {
final int oldActiveGameId = this.ultimateTicTacToe.getActiveField();
final var oldActiveGameViewModel = this.subGameViewModels[oldActiveGameId];
final var oldActiveGame = this.ultimateTicTacToe.getSubGame(oldActiveGameId);
final var oldActiveGameLocation = this.ultimateTicTacToe.getActiveField();
final var oldActiveGameViewModel = this.subGameViewModels.get(oldActiveGameLocation);
final var oldActiveGame = this.ultimateTicTacToe.getSubGame(oldActiveGameLocation);
oldActiveGameViewModel.deactivate();
this.ultimateTicTacToe.doPlayerMove(cell);
this.ultimateTicTacToe.doPlayerMove(CellLocation.values[cell]);
if (oldActiveGame.isFinished()) {
final String winnerText = oldActiveGame.getState().displayText;
oldActiveGameViewModel.setFinishPanel(winnerText);
}
final String newText = oldActiveGame.getCells()[cell].displayText;
final String newText = oldActiveGame.getCells().get(CellLocation.values[cell]).displayText;
oldActiveGameViewModel.setButtonText(cell, newText);
prepareNextMove();
}
public TicTacToePanelViewModel getSubGameViewModel(int subGameId) {
return this.subGameViewModels[subGameId];
return this.subGameViewModels.get(CellLocation.values[subGameId]);
}
public void setGameResultCallback(Consumer<String> gameResultCallback) {