Skip to content

Method: getCurrentPlayer()

1: /*
2: * Copyright © 2021 Fachhochschule für die Wirtschaft (FHDW) Hannover
3: *
4: * This file is part of ipspiel21-tictactoe-core.
5: *
6: * ipspiel21-tictactoe-core is free software: you can redistribute it and/or modify it under
7: * the terms of the GNU General Public License as published by the Free Software
8: * Foundation, either version 3 of the License, or (at your option) any later
9: * version.
10: *
11: * ipspiel21-tictactoe-core is distributed in the hope that it will be useful, but WITHOUT
12: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14: * details.
15: *
16: * You should have received a copy of the GNU General Public License along with
17: * ipspiel21-tictactoe-core. If not, see <http://www.gnu.org/licenses/>.
18: */
19: package de.fhdw.gaming.ipspiel21.tictactoe.core.domain.impl;
20:
21: import java.util.Collections;
22: import java.util.LinkedHashMap;
23: import java.util.Map;
24: import java.util.Objects;
25: import java.util.Optional;
26: import java.util.Set;
27:
28: import de.fhdw.gaming.core.domain.GameException;
29: import de.fhdw.gaming.core.domain.PlayerState;
30: import de.fhdw.gaming.ipspiel21.tictactoe.core.domain.TicTacToeBoard;
31: import de.fhdw.gaming.ipspiel21.tictactoe.core.domain.TicTacToeField;
32: import de.fhdw.gaming.ipspiel21.tictactoe.core.domain.TicTacToeFieldState;
33: import de.fhdw.gaming.ipspiel21.tictactoe.core.domain.TicTacToePlayer;
34: import de.fhdw.gaming.ipspiel21.tictactoe.core.domain.TicTacToePlayerBuilder;
35: import de.fhdw.gaming.ipspiel21.tictactoe.core.domain.TicTacToePosition;
36: import de.fhdw.gaming.ipspiel21.tictactoe.core.domain.TicTacToeRow;
37: import de.fhdw.gaming.ipspiel21.tictactoe.core.domain.TicTacToeState;
38:
39: /**
40: * Implements {@link TicTacToeState}.
41: */
42: @SuppressWarnings("PMD.GodClass")
43: final class TicTacToeStateImpl implements TicTacToeState {
44:
45: /**
46: * The board.
47: */
48: private final TicTacToeBoard board;
49: /**
50: * The player using crosses.
51: */
52: private final TicTacToePlayer crossesPlayer;
53: /**
54: * The player using noughts.
55: */
56: private final TicTacToePlayer noughtsPlayer;
57: /**
58: * The current player.
59: */
60: private TicTacToePlayer currentPlayer;
61: /**
62: * The states of the players.
63: */
64: private final Map<String, PlayerState> playerStates;
65: /**
66: * The outcomes of the players.
67: */
68: private final Map<String, Double> playerOutcomes;
69:
70: /**
71: * Creates an Tic Tac Toe state.
72: *
73: * @param board The board.
74: * @param crossesPlayerBuilder A builder for the player using crosses.
75: * @param noughtsPlayerBuilder A builder for the player using noughts.
76: * @param crossesIsNext {@code true} if the player using crosses is next, else the player using noughts is
77: * next.
78: * @throws GameException if the state cannot be created according to the rules of the game.
79: */
80: TicTacToeStateImpl(final TicTacToeBoard board, final TicTacToePlayerBuilder crossesPlayerBuilder,
81: final TicTacToePlayerBuilder noughtsPlayerBuilder, final boolean crossesIsNext) throws GameException {
82:
83: this.board = Objects.requireNonNull(board, "board");
84: this.crossesPlayer = Objects.requireNonNull(crossesPlayerBuilder, "crossesPlayerBuilder").build(this);
85: this.noughtsPlayer = Objects.requireNonNull(noughtsPlayerBuilder, "noughtsPlayerBuilder").build(this);
86: this.currentPlayer = crossesIsNext ? this.crossesPlayer : this.noughtsPlayer;
87: this.playerOutcomes = new LinkedHashMap<>();
88:
89: this.playerStates = new LinkedHashMap<>();
90: this.playerStates.put(this.crossesPlayer.getName(), PlayerState.PLAYING);
91: this.playerStates.put(this.noughtsPlayer.getName(), PlayerState.PLAYING);
92:
93: if (!this.crossesPlayer.isUsingCrosses()) {
94: throw new IllegalArgumentException(String.format("Player %s does not use crosses.", this.crossesPlayer));
95: }
96: if (this.noughtsPlayer.isUsingCrosses()) {
97: throw new IllegalArgumentException(String.format("Player %s does not use noughts.", this.noughtsPlayer));
98: }
99: if (this.crossesPlayer.getName().equals(this.noughtsPlayer.getName())) {
100: throw new IllegalArgumentException(
101: String.format("Both players have the same name '%s'.", this.crossesPlayer.getName()));
102: }
103: }
104:
105: /**
106: * Creates an TicTacToe state by copying an existing one.
107: *
108: * @param source The state to copy.
109: */
110: TicTacToeStateImpl(final TicTacToeStateImpl source) {
111: this.board = source.board.deepCopy();
112: this.crossesPlayer = source.crossesPlayer.deepCopy(this);
113: this.noughtsPlayer = source.noughtsPlayer.deepCopy(this);
114: this.currentPlayer = source.isCrossesPlayerCurrent() ? this.crossesPlayer : this.noughtsPlayer;
115: this.playerOutcomes = new LinkedHashMap<>(source.playerOutcomes);
116: this.playerStates = new LinkedHashMap<>();
117: this.playerStates.put(this.crossesPlayer.getName(), source.playerStates.get(this.crossesPlayer.getName()));
118: this.playerStates.put(this.noughtsPlayer.getName(), source.playerStates.get(this.noughtsPlayer.getName()));
119: }
120:
121: @Override
122: public String toString() {
123: return String.format(
124: "TicTacToeState[board=\n%s, crossesPlayer=%s, noughtsPlayer=%s, currentPlayer=%s]",
125: this.board,
126: this.crossesPlayer,
127: this.noughtsPlayer,
128: this.currentPlayer.isUsingCrosses() ? TicTacToeFieldState.CROSS : TicTacToeFieldState.NOUGHT);
129: }
130:
131: @Override
132: public boolean equals(final Object obj) {
133: if (obj instanceof TicTacToeStateImpl) {
134: final TicTacToeStateImpl other = (TicTacToeStateImpl) obj;
135: return this.board.equals(other.board) && this.crossesPlayer.equals(other.crossesPlayer)
136: && this.noughtsPlayer.equals(other.noughtsPlayer)
137: && this.isCrossesPlayerCurrent() == other.isCrossesPlayerCurrent();
138: }
139: return false;
140: }
141:
142: @Override
143: public int hashCode() {
144: return Objects.hash(this.board, this.crossesPlayer, this.noughtsPlayer, this.isCrossesPlayerCurrent());
145: }
146:
147: @Override
148: public TicTacToeStateImpl deepCopy() {
149: return new TicTacToeStateImpl(this);
150: }
151:
152: @Override
153: public Map<String, TicTacToePlayer> getPlayers() {
154: final Map<String, TicTacToePlayer> result = new LinkedHashMap<>();
155: result.put(this.crossesPlayer.getName(), this.crossesPlayer);
156: result.put(this.noughtsPlayer.getName(), this.noughtsPlayer);
157: return result;
158: }
159:
160: @Override
161: public PlayerState getPlayerState(final String playerName) {
162: final PlayerState playerState = this.playerStates.get(playerName);
163: if (playerState != null) {
164: return playerState;
165: } else {
166: throw new IllegalArgumentException(String.format("Unknown player %s.", playerName));
167: }
168: }
169:
170: @Override
171: public void setPlayerState(final String playerName, final PlayerState newState) {
172: if (this.playerStates.containsKey(playerName)) {
173: this.playerStates.put(playerName, newState);
174: if (newState.equals(PlayerState.PLAYING)) {
175: this.playerOutcomes.remove(playerName);
176: }
177: } else {
178: throw new IllegalArgumentException(String.format("Unknown player %s.", playerName));
179: }
180: }
181:
182: @Override
183: public Optional<Double> getPlayerOutcome(final String playerName) throws IllegalArgumentException {
184: if (this.playerStates.containsKey(playerName)) {
185: final Double outcome = this.playerOutcomes.get(playerName);
186: return outcome != null ? Optional.of(outcome) : TicTacToeState.super.getPlayerOutcome(playerName);
187: } else {
188: throw new IllegalArgumentException(String.format("Unknown player %s.", playerName));
189: }
190: }
191:
192: @Override
193: public void setPlayerOutcome(final String playerName, final double newOutcome) throws IllegalArgumentException {
194: if (this.getPlayerState(playerName).equals(PlayerState.PLAYING)) {
195: throw new IllegalArgumentException(String.format("Cannot set outcome for player %s.", playerName));
196: } else {
197: this.playerOutcomes.put(playerName, newOutcome);
198: }
199: }
200:
201: @Override
202: public TicTacToeBoard getBoard() {
203: return this.board;
204: }
205:
206: @Override
207: public TicTacToePlayer getCrossesPlayer() {
208: return this.crossesPlayer;
209: }
210:
211: @Override
212: public TicTacToePlayer getNoughtsPlayer() {
213: return this.noughtsPlayer;
214: }
215:
216: @Override
217: public TicTacToePlayer getCurrentPlayer() {
218: return this.currentPlayer;
219: }
220:
221: @Override
222: public void moveCompleted() {
223: final Set<TicTacToeRow> uniformRows = this.board.getRowsUniformlyMarked();
224: if (!uniformRows.isEmpty()) {
225: final TicTacToeFieldState rowState = uniformRows.iterator().next().getState().orElseThrow();
226: this.gameOver(rowState);
227: } else {
228: final Map<TicTacToePosition,
229: ? extends TicTacToeField> emptyFields = this.board.getFieldsBeing(TicTacToeFieldState.EMPTY);
230: if (emptyFields.isEmpty()) {
231: this.gameOver(TicTacToeFieldState.EMPTY);
232: }
233: }
234: }
235:
236: @Override
237: public Set<TicTacToePlayer> computeNextPlayers() {
238: if (this.currentPlayer.getState().equals(PlayerState.PLAYING)) {
239: return Collections.singleton(this.currentPlayer);
240: } else {
241: return Collections.emptySet();
242: }
243: }
244:
245: @Override
246: public void nextTurn() {
247: this.currentPlayer = this.getOtherPlayer();
248: }
249:
250: /**
251: * Returns the currently inactive player.
252: */
253: private TicTacToePlayer getOtherPlayer() {
254: if (this.isCrossesPlayerCurrent()) {
255: return this.noughtsPlayer;
256: } else {
257: return this.crossesPlayer;
258: }
259: }
260:
261: /**
262: * Returns {@code true} iff the current player uses crosses.
263: */
264: @SuppressWarnings("PMD.CompareObjectsWithEquals")
265: private boolean isCrossesPlayerCurrent() {
266: return this.currentPlayer == this.crossesPlayer;
267: }
268:
269: /**
270: * Updates the player states after the game has finished.
271: *
272: * @param rowState The state of some uniformly marked row, or {@link TicTacToeFieldState#EMPTY} if no player has won
273: * the game yet and no empty fields have remained.
274: */
275: private void gameOver(final TicTacToeFieldState rowState) {
276: switch (rowState) {
277: case CROSS:
278: this.setPlayerState(this.crossesPlayer.getName(), PlayerState.WON);
279: this.setPlayerState(this.noughtsPlayer.getName(), PlayerState.LOST);
280: break;
281: case NOUGHT:
282: this.setPlayerState(this.crossesPlayer.getName(), PlayerState.LOST);
283: this.setPlayerState(this.noughtsPlayer.getName(), PlayerState.WON);
284: break;
285: case EMPTY:
286: this.setPlayerState(this.crossesPlayer.getName(), PlayerState.DRAW);
287: this.setPlayerState(this.noughtsPlayer.getName(), PlayerState.DRAW);
288: break;
289: default:
290: throw new IllegalArgumentException(
291: String.format("TicTacToeState.gameOver() called with state %s.", rowState));
292: }
293: }
294: }