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; 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.CellState;
import de.icaotix.ultimatetictactoe.model.definitions.GameState; import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import java.util.List; import java.util.List;
import java.util.Map;
public interface ITicTacToeGame { 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(); boolean isFinished();

View File

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

View File

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

View File

@ -1,18 +1,20 @@
package de.icaotix.ultimatetictactoe.model; 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.CellState;
import de.icaotix.ultimatetictactoe.model.definitions.GameState; import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import de.icaotix.ultimatetictactoe.model.definitions.Player; import de.icaotix.ultimatetictactoe.model.definitions.Player;
import java.util.Arrays; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
public class UltimateTicTacToe implements IUltimateTicTacToe { public class UltimateTicTacToe implements IUltimateTicTacToe {
private ITicTacToeGame[] subGames; private Map<CellLocation, ITicTacToeGame> subGames;
private GameState[] masterGameStates; private Map<CellLocation, GameState> masterGameStates;
private Player currentPlayer; private Player currentPlayer;
private int activeField; private CellLocation activeField;
private GameState globalGameState; private GameState globalGameState;
public UltimateTicTacToe() { public UltimateTicTacToe() {
@ -21,26 +23,26 @@ public class UltimateTicTacToe implements IUltimateTicTacToe {
@Override @Override
public void initialize() { public void initialize() {
this.subGames = new ITicTacToeGame[9]; this.subGames = new HashMap<>();
for (int i = 0; i < this.subGames.length; i++) { this.masterGameStates = new HashMap<>();
this.subGames[i] = new TicTacToeGame(); 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 // X always starts in the center field
this.currentPlayer = Player.X; this.currentPlayer = Player.X;
this.activeField = 4; this.activeField = CellLocation.MC;
this.globalGameState = GameState.RUNNING; this.globalGameState = GameState.RUNNING;
} }
@Override @Override
public ITicTacToeGame getSubGame(int id) { public ITicTacToeGame getSubGame(CellLocation location) {
return this.subGames[id]; return this.subGames.get(location);
} }
@Override @Override
public int getActiveField() { public CellLocation getActiveField() {
return activeField; return activeField;
} }
@ -55,13 +57,13 @@ public class UltimateTicTacToe implements IUltimateTicTacToe {
} }
@Override @Override
public void doPlayerMove(int cell) { public void doPlayerMove(CellLocation cell) {
if (!getSubGame(this.activeField).getAvailableFields().contains(cell)) return; if (!getSubGame(this.activeField).getAvailableFields().contains(cell)) return;
var nextCellState = this.currentPlayer == Player.X ? CellState.X : CellState.O; var nextCellState = this.currentPlayer == Player.X ? CellState.X : CellState.O;
getSubGame(this.activeField).setCellState(cell, nextCellState); getSubGame(this.activeField).setCellState(cell, nextCellState);
this.masterGameStates[this.activeField] = getSubGame(this.activeField).getState(); this.masterGameStates.put(this.activeField, getSubGame(this.activeField).getState());
updateGameState(); updateGameState();
@ -73,13 +75,13 @@ public class UltimateTicTacToe implements IUltimateTicTacToe {
} }
private void updateGameState() { private void updateGameState() {
for (Integer[] winningCombination : TicTacToeGame.winningCombinations) { for (CellLocation[] winningCombination : TicTacToeGame.winningCombinations) {
final var interestingStates = new HashSet<GameState>(); final var interestingStates = new HashSet<GameState>();
interestingStates.add(this.masterGameStates[winningCombination[0]]); interestingStates.add(this.masterGameStates.get(winningCombination[0]));
interestingStates.add(this.masterGameStates[winningCombination[1]]); interestingStates.add(this.masterGameStates.get(winningCombination[1]));
interestingStates.add(this.masterGameStates[winningCombination[2]]); interestingStates.add(this.masterGameStates.get(winningCombination[2]));
if (interestingStates.size() == 1 && !interestingStates.contains(GameState.RUNNING)) { 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 O_WON -> this.globalGameState = GameState.O_WON;
case X_WON -> this.globalGameState = GameState.X_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; this.globalGameState = GameState.DRAW;
} }
} }
private int findNextAvailableSubGame(int startingIndex) { private CellLocation findNextAvailableSubGame(CellLocation startingLocation) {
while (this.subGames[startingIndex].isFinished()) { int startingIndex = startingLocation.ordinal();
while (this.subGames.get(startingLocation).isFinished()) {
startingIndex = (startingIndex + 1) % 9; 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.IUltimateTicTacToe;
import de.icaotix.ultimatetictactoe.model.definitions.CellLocation;
import de.icaotix.ultimatetictactoe.model.definitions.GameState; import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
public class UltimateTicTacToePanelViewModel { public class UltimateTicTacToePanelViewModel {
private final TicTacToePanelViewModel[] subGameViewModels; private final Map<CellLocation, TicTacToePanelViewModel> subGameViewModels;
private final IUltimateTicTacToe ultimateTicTacToe; private final IUltimateTicTacToe ultimateTicTacToe;
private Consumer<String> gameResultCallback; private Consumer<String> gameResultCallback;
private Consumer<String> currentPlayerCallback; private Consumer<String> currentPlayerCallback;
public UltimateTicTacToePanelViewModel(IUltimateTicTacToe ultimateTicTacToe, ViewModelFactory viewModelFactory) { public UltimateTicTacToePanelViewModel(IUltimateTicTacToe ultimateTicTacToe, ViewModelFactory viewModelFactory) {
this.subGameViewModels = new TicTacToePanelViewModel[9]; this.subGameViewModels = new HashMap<>();
for (int i = 0; i < this.subGameViewModels.length; i++) { for (CellLocation value : CellLocation.values) {
this.subGameViewModels[i] = viewModelFactory.getTicTacToePanelViewModel(this); this.subGameViewModels.put(
value,
viewModelFactory.getTicTacToePanelViewModel(this)
);
} }
this.ultimateTicTacToe = ultimateTicTacToe; this.ultimateTicTacToe = ultimateTicTacToe;
} }
@ -31,34 +37,41 @@ public class UltimateTicTacToePanelViewModel {
} }
} }
// Setup next move // Setup next move
final int fieldId = this.ultimateTicTacToe.getActiveField(); final var cellLocation = this.ultimateTicTacToe.getActiveField();
final var availableFields = this.ultimateTicTacToe.getSubGame(fieldId).getAvailableFields(); final var availableFields = this.ultimateTicTacToe
this.subGameViewModels[fieldId].activate(availableFields); .getSubGame(cellLocation)
.getAvailableFields()
.stream()
.mapToInt(Enum::ordinal)
.boxed()
.toList();
this.subGameViewModels.get(cellLocation).activate(availableFields);
this.currentPlayerCallback.accept("Current player: " this.currentPlayerCallback.accept("Current player: "
+ this.ultimateTicTacToe.getCurrentPlayer().name()); + this.ultimateTicTacToe.getCurrentPlayer().name());
} }
public void onCellClicked(int cell) { public void onCellClicked(int cell) {
final int oldActiveGameId = this.ultimateTicTacToe.getActiveField(); final var oldActiveGameLocation = this.ultimateTicTacToe.getActiveField();
final var oldActiveGameViewModel = this.subGameViewModels[oldActiveGameId]; final var oldActiveGameViewModel = this.subGameViewModels.get(oldActiveGameLocation);
final var oldActiveGame = this.ultimateTicTacToe.getSubGame(oldActiveGameId); final var oldActiveGame = this.ultimateTicTacToe.getSubGame(oldActiveGameLocation);
oldActiveGameViewModel.deactivate(); oldActiveGameViewModel.deactivate();
this.ultimateTicTacToe.doPlayerMove(cell); this.ultimateTicTacToe.doPlayerMove(CellLocation.values[cell]);
if (oldActiveGame.isFinished()) { if (oldActiveGame.isFinished()) {
final String winnerText = oldActiveGame.getState().displayText; final String winnerText = oldActiveGame.getState().displayText;
oldActiveGameViewModel.setFinishPanel(winnerText); oldActiveGameViewModel.setFinishPanel(winnerText);
} }
final String newText = oldActiveGame.getCells()[cell].displayText; final String newText = oldActiveGame.getCells().get(CellLocation.values[cell]).displayText;
oldActiveGameViewModel.setButtonText(cell, newText); oldActiveGameViewModel.setButtonText(cell, newText);
prepareNextMove(); prepareNextMove();
} }
public TicTacToePanelViewModel getSubGameViewModel(int subGameId) { public TicTacToePanelViewModel getSubGameViewModel(int subGameId) {
return this.subGameViewModels[subGameId]; return this.subGameViewModels.get(CellLocation.values[subGameId]);
} }
public void setGameResultCallback(Consumer<String> gameResultCallback) { public void setGameResultCallback(Consumer<String> gameResultCallback) {