Skip to content

Method: deepCopy()

1: /*
2: * Copyright © 2021-2023 Fachhochschule für die Wirtschaft (FHDW) Hannover
3: *
4: * This file is part of ipspiel24-VierConnects-core.
5: *
6: * ipspiel24-VierConnects-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: * ipspiel24-VierConnects-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: * ipspiel24-VierConnects-core. If not, see <http://www.gnu.org/licenses/>.
18: */
19: package de.fhdw.gaming.ipspiel24.VierConnects.core.domain.impl;
20:
21: import java.util.ArrayList;
22: import java.util.HashMap;
23: import java.util.LinkedHashMap;
24: import java.util.LinkedHashSet;
25: import java.util.List;
26: import java.util.Map;
27: import java.util.Objects;
28: import java.util.Optional;
29: import java.util.Set;
30:
31: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsBoard;
32: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsDirection;
33: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsField;
34: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsFieldState;
35: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsPosition;
36: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsRow;
37: import de.fhdw.gaming.ipspiel24.VierConnects.core.domain.VierConnectsRowType;
38:
39: /**
40: * Implements {@link VierConnectsBoard}.
41: */
42: @SuppressWarnings("PMD.GodClass")
43: final class VierConnectsBoardImpl implements VierConnectsBoard {
44:
45: /**
46: * The minimum number of rows.
47: */
48: private static final int MINIMUM_BOARD_SIZE_ROWS = 2;
49:
50: /**
51: * The minimum number of columns.
52: */
53: private static final int MINIMUM_BOARD_SIZE_COLUMNS = 2;
54:
55: /**
56: * The fields of the board, sorted first by row and then by column.
57: */
58: private final Map<Integer, Map<Integer, VierConnectsFieldImpl>> fields;
59:
60: /**
61: * Creates a Vier Connects board.
62: *
63: * @param nrOfRows The number of rows. It must be a positive number greater than or equal to 2.
64: * @param nrOfColumns The number of columns. It must be a positive number greater than or equal to 2.
65: * @throws IllegalArgumentException if the size does not meet the requirements.
66: */
67: VierConnectsBoardImpl(final int nrOfRows, final int nrOfColumns) throws IllegalArgumentException {
68: if (nrOfRows < VierConnectsBoardImpl.MINIMUM_BOARD_SIZE_ROWS
69: || nrOfColumns < VierConnectsBoardImpl.MINIMUM_BOARD_SIZE_COLUMNS) {
70: throw new IllegalArgumentException(
71: String.format(
72: "The board row number %d is not a positive number >= %d"
73: + " or the board column number %d is not a positive number >= %d ",
74: nrOfRows,
75: VierConnectsBoardImpl.MINIMUM_BOARD_SIZE_ROWS,
76: nrOfColumns,
77: VierConnectsBoardImpl.MINIMUM_BOARD_SIZE_COLUMNS));
78: }
79:
80: this.fields = new LinkedHashMap<>();
81: for (int rowIndex = 0; rowIndex < nrOfRows; ++rowIndex) {
82: final Map<Integer, VierConnectsFieldImpl> row = new HashMap<>();
83: this.fields.put(rowIndex, row);
84:
85: for (int columnIndex = 0; columnIndex < nrOfColumns; ++columnIndex) {
86: row.put(
87: columnIndex,
88: new VierConnectsFieldImpl(
89: this,
90: VierConnectsPosition.of(rowIndex, columnIndex),
91: VierConnectsFieldState.EMPTY));
92: }
93: }
94: }
95:
96: /**
97: * Copies an VierConnects board.
98: *
99: * @param source The board to copy.
100: */
101: private VierConnectsBoardImpl(final VierConnectsBoardImpl source) {
102: Objects.requireNonNull(source, "source");
103:
104: final int nrOfRows = source.getRowSize();
105: final int nrOfColumns = source.getColumnSize();
106: this.fields = new LinkedHashMap<>();
107: for (int rowIndex = 0; rowIndex < nrOfRows; ++rowIndex) {
108: final Map<Integer, VierConnectsFieldImpl> row = new HashMap<>();
109: this.fields.put(rowIndex, row);
110:
111: for (int columnIndex = 0; columnIndex < nrOfColumns; ++columnIndex) {
112: row.put(
113: columnIndex,
114: new VierConnectsFieldImpl(
115: this,
116: VierConnectsPosition.of(rowIndex, columnIndex),
117: source.getFieldAtInternal(VierConnectsPosition.of(rowIndex, columnIndex)).orElseThrow()
118: .getState()));
119: }
120: }
121: }
122:
123: @Override
124: public String toString() {
125: final StringBuilder result = new StringBuilder();
126: for (int row = 0; row < this.fields.size(); ++row) {
127: final Map<Integer, VierConnectsFieldImpl> rowMap = this.fields.get(row);
128: for (int column = 0; column < rowMap.size(); ++column) {
129: result.append(rowMap.get(column).getState().toString()).append(' ');
130: }
131: result.append('\n');
132: }
133: return result.toString();
134: }
135:
136: @Override
137: public boolean equals(final Object obj) {
138: if (obj instanceof VierConnectsBoardImpl) {
139: final VierConnectsBoardImpl other = (VierConnectsBoardImpl) obj;
140: return this.fields.equals(other.fields);
141: }
142: return false;
143: }
144:
145: @Override
146: public int hashCode() {
147: return this.fields.hashCode();
148: }
149:
150: @Override
151: public int getRowSize() {
152: return this.fields.size();
153: }
154:
155: @Override
156: public int getColumnSize() {
157: // Überprüft, ob das Feld leer ist, um eine NullPointer-Exception zu vermeiden.
158: if (this.fields.isEmpty()) {
159: return 0;
160: }
161: // Nimmt an, dass alle Zeilen dieselbe Anzahl von Spalten haben und gibt die Größe der ersten Zeile zurück.
162: final Map.Entry<Integer,
163: Map<Integer, VierConnectsFieldImpl>> firstEntry = this.fields.entrySet().iterator().next();
164: return firstEntry.getValue().size();
165:
166: // return this.fields.size();
167: }
168:
169: @Override
170: public boolean hasFieldAt(final VierConnectsPosition position) {
171: final int nrOfRows = this.getRowSize();
172: final int nrOfColumns = this.getColumnSize();
173: final int row = position.getRow();
174: final int column = position.getColumn();
175: return row >= 0 && row < nrOfRows && column >= 0 && column < nrOfColumns;
176: }
177:
178: @Override
179: public VierConnectsFieldImpl getFieldAt(final VierConnectsPosition position) {
180: if (!this.hasFieldAt(position)) {
181: throw new IllegalArgumentException(String.format("Position %s out of range.", position));
182: }
183:
184: return this.getFieldAtInternal(position).orElseThrow();
185: }
186:
187: @Override
188: public List<List<? extends VierConnectsField>> getFields() {
189: final List<List<? extends VierConnectsField>> result = new ArrayList<>();
190: this.fields.values()
191: .forEach((final Map<Integer, VierConnectsFieldImpl> row) -> result.add(new ArrayList<>(row.values())));
192: return result;
193: }
194:
195: @Override
196: public Set<VierConnectsRow> getRowsWithFourMarked() {
197: final Set<VierConnectsRow> result = new LinkedHashSet<>();
198: for (int rowIndex = 0; rowIndex < this.getRowSize(); ++rowIndex) {
199: addRowIfFourMarked(result, this.getRowOfFields(VierConnectsRowType.ROW, rowIndex));
200: }
201: for (int columnIndex = 0; columnIndex < this.getColumnSize(); ++columnIndex) {
202: addRowIfFourMarked(result, this.getRowOfFields(VierConnectsRowType.COLUMN, columnIndex));
203: }
204: for (int diagonalIndexRow = 0; diagonalIndexRow < this.getRowSize(); ++diagonalIndexRow) {
205: addRowIfFourMarked(result, this.getRowOfFields(VierConnectsRowType.DIAGONAL, diagonalIndexRow, true));
206: }
207: for (int diagonalIndexColumn = 0; diagonalIndexColumn < this.getColumnSize(); ++diagonalIndexColumn) {
208: addRowIfFourMarked(result, this.getRowOfFields(VierConnectsRowType.DIAGONAL, diagonalIndexColumn, false));
209: }
210: for (int diagonalIndexRow = this.getRowSize() - 1; diagonalIndexRow >= 0; --diagonalIndexRow) {
211: addRowIfFourMarked(result, this.getRowOfFields(VierConnectsRowType.DIAGONALALT, diagonalIndexRow, true));
212: }
213: for (int diagonalIndexColumn = this.getColumnSize() - 1; diagonalIndexColumn >= 0; --diagonalIndexColumn) {
214: addRowIfFourMarked(result, this.getRowOfFields(VierConnectsRowType.DIAGONALALT,
215: diagonalIndexColumn, false));
216: }
217: return result;
218: }
219:
220: @Override
221: public Map<VierConnectsPosition, VierConnectsFieldImpl> getFieldsBeing(final VierConnectsFieldState fieldState) {
222: final Map<VierConnectsPosition, VierConnectsFieldImpl> result = new LinkedHashMap<>();
223: final int nrOfRows = this.getRowSize();
224: final int nrOfColumns = this.getColumnSize();
225: for (int rowIndex = 0; rowIndex < nrOfRows; ++rowIndex) {
226: for (int columnIndex = 0; columnIndex < nrOfColumns; ++columnIndex) {
227: final VierConnectsPosition position = VierConnectsPosition.of(rowIndex, columnIndex);
228: final VierConnectsFieldImpl field = this.getFieldAtInternal(position).orElseThrow();
229: if (field.getState().equals(fieldState)) {
230: result.put(position, field);
231: }
232: }
233: }
234:
235: return result;
236: }
237:
238: /**
239: * Adds a row to a set of rows if it has 4 of the same type in a line.
240: *
241: * @param result The result set.
242: * @param row The row to add.
243: */
244: private static void addRowIfFourMarked(final Set<VierConnectsRow> result, final VierConnectsRow row) {
245: final Optional<VierConnectsFieldState> rowState = row.getState();
246: if (rowState.isPresent() && !rowState.get().equals(VierConnectsFieldState.EMPTY)) {
247: result.add(row);
248: }
249: }
250:
251: @Override
252: public VierConnectsBoardImpl deepCopy() {
253: return new VierConnectsBoardImpl(this);
254: }
255:
256: /**
257: * Returns a row of fields given the row type and the row index.
258: *
259: * @param type The row type.
260: * @param index The row index.
261: */
262: private VierConnectsRow getRowOfFields(final VierConnectsRowType type, final int index) {
263: final List<? extends VierConnectsField> fieldsOfRow;
264:
265: switch (type) {
266: case ROW:
267: fieldsOfRow = this.getFields(VierConnectsPosition.of(index, 0), VierConnectsDirection.EAST);
268: break;
269: case COLUMN:
270: fieldsOfRow = this.getFields(VierConnectsPosition.of(0, index), VierConnectsDirection.SOUTH);
271: break;
272: default:
273: throw new IllegalArgumentException(String.format("Unknown VierConnectsRowType %s.", type));
274: }
275:
276: return new VierConnectsRowImpl(type, index, fieldsOfRow);
277: }
278:
279: /**
280: * Returns a row of fields given the row type and the row index.
281: *
282: * @param type The row type.
283: * @param index The row index.
284: * @param isBelowDisecting If below disecting.
285: */
286: private VierConnectsRow getRowOfFields(final VierConnectsRowType type,
287: final int index,
288: final boolean isBelowDisecting) {
289: final List<? extends VierConnectsField> fieldsOfRow;
290:
291: switch (type) {
292: case DIAGONAL:
293: if (isBelowDisecting) {
294: fieldsOfRow = this.getFields(VierConnectsPosition.of(index, 0), VierConnectsDirection.SOUTHEAST);
295: } else {
296: fieldsOfRow = this.getFields(VierConnectsPosition.of(0, index), VierConnectsDirection.SOUTHEAST);
297: }
298: break;
299: case DIAGONALALT:
300: if (isBelowDisecting) {
301: fieldsOfRow = this.getFields(VierConnectsPosition.of(index, this.getColumnSize() - 1),
302: VierConnectsDirection.SOUTHWEST);
303: } else {
304: fieldsOfRow = this.getFields(VierConnectsPosition.of(0, index),
305: VierConnectsDirection.SOUTHWEST);
306: }
307: break;
308: default:
309: throw new IllegalArgumentException(String.format("Unknown VierConnectsRowType %s.", type));
310: }
311:
312: return new VierConnectsRowImpl(type, index, fieldsOfRow);
313: }
314:
315: // /**
316: // * Returns a row of fields that is diagonal, the index will count along
317: // * the rows if isBelowDisecting is true, along the columns if it is false.
318: // *
319: // * @param isBelowDisecting If the diagonals are counted along the columns or the rows.
320: // * @param index The row index.
321: // */
322: // private VierConnectsRow getRowOfFieldsDiagonal(final boolean isBelowDisecting, final int index) {
323: // final List<? extends VierConnectsField> fieldsOfRow;
324: // if (isBelowDisecting) {
325: // fieldsOfRow = this.getFields(VierConnectsPosition.of(index, 0), VierConnectsDirection.SOUTHEAST);
326: // } else {
327: // fieldsOfRow = this.getFields(VierConnectsPosition.of(0, index), VierConnectsDirection.SOUTHEAST);
328: // }
329: //
330: // return new VierConnectsRowImpl(VierConnectsRowType.DIAGONAL, index, fieldsOfRow);
331: // }
332: //
333: // /**
334: // * Returns a row of fields that is diagonal, the index will count along
335: // * the rows if isBelowDisecting is true, along the columns if it is false.
336: // *
337: // * @param isBelowDisecting If the diagonals are counted along the columns or the rows.
338: // * @param index The row index.
339: // */
340: // private VierConnectsRow getRowOfFieldsDiagonalAlt(final boolean isBelowDisecting, final int index) {
341: // final List<? extends VierConnectsField> fieldsOfRow;
342: // if (isBelowDisecting) {
343: // fieldsOfRow = this.getFields(VierConnectsPosition.of(index, this.getColumnSize() - 1),
344: // VierConnectsDirection.SOUTHWEST);
345: // } else {
346: // fieldsOfRow = this.getFields(VierConnectsPosition.of(0, index),
347: // VierConnectsDirection.SOUTHWEST);
348: // }
349: //
350: // return new VierConnectsRowImpl(VierConnectsRowType.DIAGONAL, index, fieldsOfRow);
351: // }
352:
353: /**
354: * Returns a list of fields starting at a given position and extending towards a given direction till the board's
355: * end.
356: *
357: * @param start The starting position.
358: * @param direction The direction.
359: */
360: private List<? extends VierConnectsField> getFields(final VierConnectsPosition start,
361: final VierConnectsDirection direction) {
362: final List<VierConnectsField> result = new ArrayList<>();
363:
364: VierConnectsPosition pos = start;
365: Optional<VierConnectsFieldImpl> field = this.getFieldAtInternal(pos);
366: while (field.isPresent()) {
367: result.add(field.get());
368: pos = direction.step(pos);
369: field = this.getFieldAtInternal(pos);
370: }
371:
372: return result;
373: }
374:
375: /**
376: * Returns a field at a given position. If the position does not exist, {@link Optional#empty()} is returned.
377: *
378: * @param position The position of the field.
379: * @return The field.
380: */
381: private Optional<VierConnectsFieldImpl> getFieldAtInternal(final VierConnectsPosition position) {
382: return Optional.ofNullable(this.fields.get(position.getRow()))
383: .map((final Map<Integer, VierConnectsFieldImpl> map) -> map.get(position.getColumn()));
384: }
385: }