Skip to content

Method: nextTurn()

1: /*
2: * Copyright © 2021 Fachhochschule für die Wirtschaft (FHDW) Hannover
3: *
4: * This file is part of ipspiel23-tictactoe-core.
5: *
6: * ipspiel23-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: * ipspiel23-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: * ipspiel23-tictactoe-core. If not, see <http://www.gnu.org/licenses/>.
18: */
19: package de.fhdw.gaming.ipspiel23.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.Set;
26:
27: import de.fhdw.gaming.core.domain.GameException;
28: import de.fhdw.gaming.core.domain.PlayerState;
29: import de.fhdw.gaming.ipspiel23.tictactoe.core.domain.TicTacToeBoard;
30: import de.fhdw.gaming.ipspiel23.tictactoe.core.domain.TicTacToeField;
31: import de.fhdw.gaming.ipspiel23.tictactoe.core.domain.TicTacToeFieldState;
32: import de.fhdw.gaming.ipspiel23.tictactoe.core.domain.TicTacToePlayer;
33: import de.fhdw.gaming.ipspiel23.tictactoe.core.domain.TicTacToePosition;
34: import de.fhdw.gaming.ipspiel23.tictactoe.core.domain.TicTacToeRow;
35: import de.fhdw.gaming.ipspiel23.tictactoe.core.domain.TicTacToeState;
36:
37: /**
38: * Implements {@link TicTacToeState}.
39: */
40: @SuppressWarnings("PMD.GodClass")
41: final class TicTacToeStateImpl implements TicTacToeState {
42:
43: /**
44: * The board.
45: */
46: private final TicTacToeBoard board;
47:
48: /**
49: * The player using crosses.
50: */
51: private final TicTacToePlayer crossesPlayer;
52:
53: /**
54: * The player using noughts.
55: */
56: private final TicTacToePlayer noughtsPlayer;
57:
58: /**
59: * The current player.
60: */
61: private TicTacToePlayer currentPlayer;
62:
63: /**
64: * Creates an Tic Tac Toe state.
65: *
66: * @param board The board.
67: * @param crossesPlayer The player using crosses.
68: * @param noughtsPlayer The player using noughts.
69: * @param crossesIsNext {@code true} if the player using crosses is next, else the player using noughts is next.
70: * @throws GameException if the state cannot be created according to the rules of the game.
71: */
72: TicTacToeStateImpl(final TicTacToeBoard board, final TicTacToePlayer crossesPlayer,
73: final TicTacToePlayer noughtsPlayer, final boolean crossesIsNext) throws GameException {
74:
75: this.board = Objects.requireNonNull(board, "board");
76: this.crossesPlayer = Objects.requireNonNull(crossesPlayer, "crossesPlayer");
77: this.noughtsPlayer = Objects.requireNonNull(noughtsPlayer, "noughtsPlayer");
78: this.currentPlayer = crossesIsNext ? this.crossesPlayer : this.noughtsPlayer;
79:
80: if (!this.crossesPlayer.isUsingCrosses()) {
81: throw new IllegalArgumentException(String.format("Player %s does not use crosses.", this.crossesPlayer));
82: }
83: if (this.noughtsPlayer.isUsingCrosses()) {
84: throw new IllegalArgumentException(String.format("Player %s does not use noughts.", this.noughtsPlayer));
85: }
86: if (this.crossesPlayer.getName().equals(this.noughtsPlayer.getName())) {
87: throw new IllegalArgumentException(
88: String.format("Both players have the same name '%s'.", this.crossesPlayer.getName()));
89: }
90: }
91:
92: /**
93: * Creates an TicTacToe state by copying an existing one.
94: *
95: * @param source The state to copy.
96: */
97: TicTacToeStateImpl(final TicTacToeStateImpl source) {
98: this.board = source.board.deepCopy();
99: this.crossesPlayer = source.crossesPlayer.deepCopy();
100: this.noughtsPlayer = source.noughtsPlayer.deepCopy();
101: this.currentPlayer = source.isCrossesPlayerCurrent() ? this.crossesPlayer : this.noughtsPlayer;
102: }
103:
104: @Override
105: public String toString() {
106: return String.format(
107: "TicTacToeState[board=\n%s, crossesPlayer=%s, noughtsPlayer=%s, currentPlayer=%s]",
108: this.board,
109: this.crossesPlayer,
110: this.noughtsPlayer,
111: this.currentPlayer.isUsingCrosses() ? TicTacToeFieldState.CROSS : TicTacToeFieldState.NOUGHT);
112: }
113:
114: @Override
115: public boolean equals(final Object obj) {
116: if (obj instanceof TicTacToeStateImpl) {
117: final TicTacToeStateImpl other = (TicTacToeStateImpl) obj;
118: return this.board.equals(other.board) && this.crossesPlayer.equals(other.crossesPlayer)
119: && this.noughtsPlayer.equals(other.noughtsPlayer)
120: && this.isCrossesPlayerCurrent() == other.isCrossesPlayerCurrent();
121: }
122: return false;
123: }
124:
125: @Override
126: public int hashCode() {
127: return Objects.hash(this.board, this.crossesPlayer, this.noughtsPlayer, this.isCrossesPlayerCurrent());
128: }
129:
130: @Override
131: public TicTacToeStateImpl deepCopy() {
132: return new TicTacToeStateImpl(this);
133: }
134:
135: @Override
136: public Map<String, TicTacToePlayer> getPlayers() {
137: final Map<String, TicTacToePlayer> result = new LinkedHashMap<>();
138: result.put(this.crossesPlayer.getName(), this.crossesPlayer);
139: result.put(this.noughtsPlayer.getName(), this.noughtsPlayer);
140: return result;
141: }
142:
143: @Override
144: public TicTacToeBoard getBoard() {
145: return this.board;
146: }
147:
148: @Override
149: public TicTacToePlayer getCrossesPlayer() {
150: return this.crossesPlayer;
151: }
152:
153: @Override
154: public TicTacToePlayer getNoughtsPlayer() {
155: return this.noughtsPlayer;
156: }
157:
158: @Override
159: public TicTacToePlayer getCurrentPlayer() {
160: return this.currentPlayer;
161: }
162:
163: @Override
164: public void moveCompleted() {
165: final Set<TicTacToeRow> uniformRows = this.board.getRowsUniformlyMarked();
166: if (!uniformRows.isEmpty()) {
167: final TicTacToeFieldState rowState = uniformRows.iterator().next().getState().orElseThrow();
168: this.gameOver(rowState);
169: } else {
170: final Map<TicTacToePosition,
171: ? extends TicTacToeField> emptyFields = this.board.getFieldsBeing(TicTacToeFieldState.EMPTY);
172: if (emptyFields.isEmpty()) {
173: this.gameOver(TicTacToeFieldState.EMPTY);
174: }
175: }
176: }
177:
178: @Override
179: public Set<TicTacToePlayer> computeNextPlayers() {
180: if (this.currentPlayer.getState().equals(PlayerState.PLAYING)) {
181: return Collections.singleton(this.currentPlayer);
182: }
183: return Collections.emptySet();
184: }
185:
186: @Override
187: public void nextTurn() {
188: this.currentPlayer = this.getOtherPlayer();
189: }
190:
191: /**
192: * Returns the currently inactive player.
193: */
194: private TicTacToePlayer getOtherPlayer() {
195: if (this.isCrossesPlayerCurrent()) {
196: return this.noughtsPlayer;
197: }
198: return this.crossesPlayer;
199: }
200:
201: /**
202: * Returns {@code true} iff the current player uses crosses.
203: */
204: @SuppressWarnings("PMD.CompareObjectsWithEquals")
205: private boolean isCrossesPlayerCurrent() {
206: return this.currentPlayer == this.crossesPlayer;
207: }
208:
209: /**
210: * Updates the player states after the game has finished.
211: *
212: * @param rowState The state of some uniformly marked row, or {@link TicTacToeFieldState#EMPTY} if no player has won
213: * the game yet and no empty fields have remained.
214: */
215: private void gameOver(final TicTacToeFieldState rowState) {
216: switch (rowState) {
217: case CROSS:
218: this.crossesPlayer.setState(PlayerState.WON);
219: this.noughtsPlayer.setState(PlayerState.LOST);
220: break;
221: case NOUGHT:
222: this.crossesPlayer.setState(PlayerState.LOST);
223: this.noughtsPlayer.setState(PlayerState.WON);
224: break;
225: case EMPTY:
226: this.crossesPlayer.setState(PlayerState.DRAW);
227: this.noughtsPlayer.setState(PlayerState.DRAW);
228: break;
229: default:
230: throw new IllegalArgumentException(
231: String.format("TicTacToeState.gameOver() called with state %s.", rowState));
232: }
233: }
234: }