Introduce Model and ViewModel factories, Extract interfaces out of models

Replace some duplicated classnames with var keyword
Fix a hierarchy violation in the view class
This commit is contained in:
Marcel Schwarz 2021-11-03 23:20:53 +01:00
parent 2cfa7130fc
commit 016d6db839
13 changed files with 139 additions and 55 deletions

View File

@ -1,7 +1,8 @@
package de.icaotix.ultimatetictactoe; package de.icaotix.ultimatetictactoe;
import de.icaotix.ultimatetictactoe.model.ModelFactory;
import de.icaotix.ultimatetictactoe.view.UltimateTicTacToeView; import de.icaotix.ultimatetictactoe.view.UltimateTicTacToeView;
import de.icaotix.ultimatetictactoe.viewmodel.UltimateTicTacToePanelViewModel; import de.icaotix.ultimatetictactoe.viewmodel.ViewModelFactory;
import javax.swing.*; import javax.swing.*;
@ -9,7 +10,11 @@ public class Main {
public static void main(String[] args) { public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
final UltimateTicTacToePanelViewModel ultimateTicTacToePanelViewModel = new UltimateTicTacToePanelViewModel();
ModelFactory modelFactory = new ModelFactory();
ViewModelFactory viewModelFactory = new ViewModelFactory(modelFactory);
final var ultimateTicTacToePanelViewModel = viewModelFactory.getUltimateTicTacToePanelViewModel();
new UltimateTicTacToeView(ultimateTicTacToePanelViewModel); new UltimateTicTacToeView(ultimateTicTacToePanelViewModel);
ultimateTicTacToePanelViewModel.prepareNextMove(); ultimateTicTacToePanelViewModel.prepareNextMove();
}); });

View File

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

View File

@ -0,0 +1,16 @@
package de.icaotix.ultimatetictactoe.model;
import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import de.icaotix.ultimatetictactoe.model.definitions.Player;
public interface IUltimateTicTacToe {
ITicTacToeGame getSubGame(int id);
int getActiveField();
Player getCurrentPlayer();
GameState getGameState();
void doPlayerMove(int cell);
}

View File

@ -0,0 +1,14 @@
package de.icaotix.ultimatetictactoe.model;
public class ModelFactory {
private final UltimateTicTacToe ultimateTicTacToe;
public ModelFactory() {
this.ultimateTicTacToe = new UltimateTicTacToe();
}
public IUltimateTicTacToe getUltimateTicTacToe() {
return ultimateTicTacToe;
}
}

View File

@ -1,6 +0,0 @@
package de.icaotix.ultimatetictactoe.model;
public enum Player {
X,
O
}

View File

@ -1,11 +1,14 @@
package de.icaotix.ultimatetictactoe.model; package de.icaotix.ultimatetictactoe.model;
import de.icaotix.ultimatetictactoe.model.definitions.CellState;
import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
public class TicTacToeGame { public class TicTacToeGame implements ITicTacToeGame {
public static final Integer[][] winningCombinations = new Integer[][]{ public static final Integer[][] winningCombinations = new Integer[][]{
{0, 1, 2}, {0, 1, 2},
@ -27,6 +30,22 @@ public class TicTacToeGame {
this.state = GameState.RUNNING; 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) {
System.out.println();
}
}
System.out.println("FREE: " + game.getAvailableFields());
System.out.println("STATE: " + game.getState());
System.out.println("IS FINISHED: " + game.isFinished());
}
@Override
public void setCellState(int cell, CellState state) { public void setCellState(int cell, CellState state) {
if (this.state != GameState.RUNNING) return; if (this.state != GameState.RUNNING) return;
if (this.cells[cell] == CellState.EMPTY) { if (this.cells[cell] == CellState.EMPTY) {
@ -35,10 +54,12 @@ public class TicTacToeGame {
} }
} }
@Override
public CellState[] getCells() { public CellState[] getCells() {
return cells; return cells;
} }
@Override
public List<Integer> getAvailableFields() { public List<Integer> getAvailableFields() {
final LinkedList<Integer> emptyFields = new LinkedList<>(); final LinkedList<Integer> emptyFields = new LinkedList<>();
for (int i = 0; i < this.cells.length; i++) { for (int i = 0; i < this.cells.length; i++) {
@ -49,17 +70,19 @@ public class TicTacToeGame {
return emptyFields; return emptyFields;
} }
@Override
public boolean isFinished() { public boolean isFinished() {
return this.state != GameState.RUNNING; return this.state != GameState.RUNNING;
} }
@Override
public GameState getState() { public GameState getState() {
return state; return state;
} }
private void updateGameState() { private void updateGameState() {
for (Integer[] winningCombination : winningCombinations) { for (Integer[] winningCombination : winningCombinations) {
final HashSet<CellState> interestingStates = new HashSet<>(); final var interestingStates = new HashSet<CellState>();
interestingStates.add(this.cells[winningCombination[0]]); interestingStates.add(this.cells[winningCombination[0]]);
interestingStates.add(this.cells[winningCombination[1]]); interestingStates.add(this.cells[winningCombination[1]]);
interestingStates.add(this.cells[winningCombination[2]]); interestingStates.add(this.cells[winningCombination[2]]);
@ -77,19 +100,4 @@ public class TicTacToeGame {
} }
} }
// DEBUG
public static void printField(TicTacToeGame game) {
final CellState[] 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) {
System.out.println();
}
}
System.out.println("FREE: " + game.getAvailableFields());
System.out.println("STATE: " + game.getState());
System.out.println("IS FINISHED: " + game.isFinished());
}
} }

View File

@ -1,18 +1,22 @@
package de.icaotix.ultimatetictactoe.model; package de.icaotix.ultimatetictactoe.model;
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.Arrays;
import java.util.HashSet; import java.util.HashSet;
public class UltimateTicTacToe { public class UltimateTicTacToe implements IUltimateTicTacToe {
private final TicTacToeGame[] subGames; private final ITicTacToeGame[] subGames;
private final GameState[] masterGameStates; private final GameState[] masterGameStates;
private Player currentPlayer; private Player currentPlayer;
private int activeField; private int activeField;
private GameState gameState; private GameState gameState;
public UltimateTicTacToe() { public UltimateTicTacToe() {
this.subGames = new TicTacToeGame[9]; this.subGames = new ITicTacToeGame[9];
for (int i = 0; i < this.subGames.length; i++) { for (int i = 0; i < this.subGames.length; i++) {
this.subGames[i] = new TicTacToeGame(); this.subGames[i] = new TicTacToeGame();
} }
@ -25,26 +29,31 @@ public class UltimateTicTacToe {
this.gameState = GameState.RUNNING; this.gameState = GameState.RUNNING;
} }
public TicTacToeGame getSubGame(int id) { @Override
public ITicTacToeGame getSubGame(int id) {
return this.subGames[id]; return this.subGames[id];
} }
@Override
public int getActiveField() { public int getActiveField() {
return activeField; return activeField;
} }
@Override
public Player getCurrentPlayer() { public Player getCurrentPlayer() {
return this.currentPlayer; return this.currentPlayer;
} }
@Override
public GameState getGameState() { public GameState getGameState() {
return gameState; return gameState;
} }
@Override
public void doPlayerMove(int cell) { public void doPlayerMove(int cell) {
if (!getSubGame(this.activeField).getAvailableFields().contains(cell)) return; if (!getSubGame(this.activeField).getAvailableFields().contains(cell)) return;
CellState 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[this.activeField] = getSubGame(this.activeField).getState();
@ -60,7 +69,7 @@ public class UltimateTicTacToe {
private void updateGameState() { private void updateGameState() {
for (Integer[] winningCombination : TicTacToeGame.winningCombinations) { for (Integer[] winningCombination : TicTacToeGame.winningCombinations) {
final HashSet<GameState> interestingStates = new HashSet<>(); final var interestingStates = new HashSet<GameState>();
interestingStates.add(this.masterGameStates[winningCombination[0]]); interestingStates.add(this.masterGameStates[winningCombination[0]]);
interestingStates.add(this.masterGameStates[winningCombination[1]]); interestingStates.add(this.masterGameStates[winningCombination[1]]);
interestingStates.add(this.masterGameStates[winningCombination[2]]); interestingStates.add(this.masterGameStates[winningCombination[2]]);

View File

@ -1,4 +1,4 @@
package de.icaotix.ultimatetictactoe.model; package de.icaotix.ultimatetictactoe.model.definitions;
public enum CellState { public enum CellState {
X("X"), X("X"),

View File

@ -1,4 +1,4 @@
package de.icaotix.ultimatetictactoe.model; package de.icaotix.ultimatetictactoe.model.definitions;
public enum GameState { public enum GameState {
X_WON("X won"), X_WON("X won"),

View File

@ -0,0 +1,6 @@
package de.icaotix.ultimatetictactoe.model.definitions;
public enum Player {
X,
O
}

View File

@ -1,6 +1,5 @@
package de.icaotix.ultimatetictactoe.view; package de.icaotix.ultimatetictactoe.view;
import de.icaotix.ultimatetictactoe.model.Player;
import de.icaotix.ultimatetictactoe.viewmodel.TicTacToePanelViewModel; import de.icaotix.ultimatetictactoe.viewmodel.TicTacToePanelViewModel;
import de.icaotix.ultimatetictactoe.viewmodel.UltimateTicTacToePanelViewModel; import de.icaotix.ultimatetictactoe.viewmodel.UltimateTicTacToePanelViewModel;
@ -53,11 +52,7 @@ public class UltimateTicTacToeView extends JFrame {
this.getContentPane().repaint(); this.getContentPane().repaint();
} }
public void setCurrentPlayer(Player player) { public void setCurrentPlayer(String playerText) {
if (player == Player.O) { this.currentPlayerLabel.setText(playerText);
this.currentPlayerLabel.setText("Current player: O");
} else if (player == Player.X) {
this.currentPlayerLabel.setText("Current player: X");
}
} }
} }

View File

@ -1,27 +1,25 @@
package de.icaotix.ultimatetictactoe.viewmodel; package de.icaotix.ultimatetictactoe.viewmodel;
import de.icaotix.ultimatetictactoe.model.GameState; import de.icaotix.ultimatetictactoe.model.IUltimateTicTacToe;
import de.icaotix.ultimatetictactoe.model.Player; import de.icaotix.ultimatetictactoe.model.ModelFactory;
import de.icaotix.ultimatetictactoe.model.TicTacToeGame; import de.icaotix.ultimatetictactoe.model.definitions.GameState;
import de.icaotix.ultimatetictactoe.model.UltimateTicTacToe;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
public class UltimateTicTacToePanelViewModel { public class UltimateTicTacToePanelViewModel {
private final TicTacToePanelViewModel[] subGameViewModels; private final TicTacToePanelViewModel[] subGameViewModels;
private final UltimateTicTacToe ultimateTicTacToe; private final IUltimateTicTacToe ultimateTicTacToe;
private Consumer<String> gameResultCallback; private Consumer<String> gameResultCallback;
private Consumer<Player> currentPlayerCallback; private Consumer<String> currentPlayerCallback;
public UltimateTicTacToePanelViewModel() { public UltimateTicTacToePanelViewModel(ModelFactory modelFactory, ViewModelFactory viewModelFactory) {
this.subGameViewModels = new TicTacToePanelViewModel[9]; this.subGameViewModels = new TicTacToePanelViewModel[9];
for (int i = 0; i < this.subGameViewModels.length; i++) { for (int i = 0; i < this.subGameViewModels.length; i++) {
this.subGameViewModels[i] = new TicTacToePanelViewModel(this); this.subGameViewModels[i] = viewModelFactory.getTicTacToePanelViewModel(this);
} }
this.ultimateTicTacToe = new UltimateTicTacToe(); this.ultimateTicTacToe = modelFactory.getUltimateTicTacToe();
} }
public void prepareNextMove() { public void prepareNextMove() {
@ -35,15 +33,16 @@ public class UltimateTicTacToePanelViewModel {
} }
// Setup next move // Setup next move
final int fieldId = this.ultimateTicTacToe.getActiveField(); final int fieldId = this.ultimateTicTacToe.getActiveField();
final List<Integer> availableFields = this.ultimateTicTacToe.getSubGame(fieldId).getAvailableFields(); final var availableFields = this.ultimateTicTacToe.getSubGame(fieldId).getAvailableFields();
this.subGameViewModels[fieldId].activate(availableFields); this.subGameViewModels[fieldId].activate(availableFields);
this.currentPlayerCallback.accept(this.ultimateTicTacToe.getCurrentPlayer()); this.currentPlayerCallback.accept("Current player: "
+ this.ultimateTicTacToe.getCurrentPlayer().name());
} }
public void onCellClicked(int cell) { public void onCellClicked(int cell) {
final int oldActiveGameId = this.ultimateTicTacToe.getActiveField(); final int oldActiveGameId = this.ultimateTicTacToe.getActiveField();
final TicTacToePanelViewModel oldActiveGameViewModel = this.subGameViewModels[oldActiveGameId]; final var oldActiveGameViewModel = this.subGameViewModels[oldActiveGameId];
final TicTacToeGame oldActiveGame = this.ultimateTicTacToe.getSubGame(oldActiveGameId); final var oldActiveGame = this.ultimateTicTacToe.getSubGame(oldActiveGameId);
oldActiveGameViewModel.deactivate(); oldActiveGameViewModel.deactivate();
this.ultimateTicTacToe.doPlayerMove(cell); this.ultimateTicTacToe.doPlayerMove(cell);
@ -67,7 +66,7 @@ public class UltimateTicTacToePanelViewModel {
this.gameResultCallback = gameResultCallback; this.gameResultCallback = gameResultCallback;
} }
public void setCurrentPlayerCallback(Consumer<Player> currentPlayerCallback) { public void setCurrentPlayerCallback(Consumer<String> currentPlayerCallback) {
this.currentPlayerCallback = currentPlayerCallback; this.currentPlayerCallback = currentPlayerCallback;
} }
} }

View File

@ -0,0 +1,20 @@
package de.icaotix.ultimatetictactoe.viewmodel;
import de.icaotix.ultimatetictactoe.model.ModelFactory;
public class ViewModelFactory {
private final ModelFactory modelFactory;
public ViewModelFactory(ModelFactory modelFactory) {
this.modelFactory = modelFactory;
}
public UltimateTicTacToePanelViewModel getUltimateTicTacToePanelViewModel() {
return new UltimateTicTacToePanelViewModel(this.modelFactory, this);
}
public TicTacToePanelViewModel getTicTacToePanelViewModel(UltimateTicTacToePanelViewModel ultimateTicTacToePanelViewModel) {
return new TicTacToePanelViewModel(ultimateTicTacToePanelViewModel);
}
}