Skip to content

Method: build(int)

1: package de.fhdw.gaming.ipspiel23.c4.domain.impl;
2:
3: import java.util.ArrayList;
4: import java.util.Collections;
5: import java.util.HashMap;
6: import java.util.List;
7: import java.util.Map;
8: import java.util.Objects;
9: import java.util.concurrent.atomic.AtomicInteger;
10:
11: import de.fhdw.gaming.core.domain.DefaultObserverFactoryProvider;
12: import de.fhdw.gaming.core.domain.GameException;
13: import de.fhdw.gaming.core.domain.ObserverFactoryProvider;
14: import de.fhdw.gaming.ipspiel23.c4.collections.IReadOnlyDictionary;
15: import de.fhdw.gaming.ipspiel23.c4.collections.ReadOnlyDictionary;
16: import de.fhdw.gaming.ipspiel23.c4.domain.IC4Board;
17: import de.fhdw.gaming.ipspiel23.c4.domain.IC4BoardSlim;
18: import de.fhdw.gaming.ipspiel23.c4.domain.IC4Game;
19: import de.fhdw.gaming.ipspiel23.c4.domain.IC4GameBuilder;
20: import de.fhdw.gaming.ipspiel23.c4.domain.IC4Player;
21: import de.fhdw.gaming.ipspiel23.c4.domain.IC4PlayerBuilder;
22: import de.fhdw.gaming.ipspiel23.c4.domain.IC4State;
23: import de.fhdw.gaming.ipspiel23.c4.moves.IC4Move;
24: import de.fhdw.gaming.ipspiel23.c4.strategies.IC4Strategy;
25:
26: /**
27: * The {@link IC4GameBuilder} implementation.
28: */
29: public final class C4GameBuilder implements IC4GameBuilder {
30:
31: /**
32: * The {@link ObserverFactoryProvider}.
33: */
34: private ObserverFactoryProvider observerFactoryProvider;
35:
36: /**
37: * The maximum computation time per move in seconds.
38: */
39: private int maxComputationTimePerMove;
40:
41: /**
42: * The number of rows of the board.
43: */
44: private int rowCount;
45:
46: /**
47: * The number of columns of the board.
48: */
49: private int columnCount;
50:
51: /**
52: * The required solution size.
53: */
54: private int solutionSize;
55:
56: /**
57: * The current player id used to identify the next player.
58: */
59: private final AtomicInteger currentPlayerId;
60:
61: /**
62: * The players added to the game so far.
63: */
64: private final List<IC4Player> players;
65:
66: /**
67: * The player builders used to create the players.
68: */
69: private final Map<Integer, C4PlayerBuilder> playerBuilders;
70:
71: /**
72: * Creates a new {@link C4GameBuilder} instance.
73: */
74: public C4GameBuilder() {
75: this.players = new ArrayList<>(2);
76: this.currentPlayerId = new AtomicInteger(0);
77: this.playerBuilders = new HashMap<>(2);
78: this.maxComputationTimePerMove = IC4GameBuilder.DEFAULT_MAX_COMPUTATION_TIME_PER_MOVE;
79: this.rowCount = IC4GameBuilder.DEFAULT_BOARD_ROWS;
80: this.columnCount = IC4GameBuilder.DEFAULT_BOARD_COLUMNS;
81: this.solutionSize = IC4GameBuilder.DEFAULT_REQUIRED_SOLUTION_SIZE;
82: this.observerFactoryProvider = new DefaultObserverFactoryProvider();
83: }
84:
85: @Override
86: public C4GameBuilder changeMaximumComputationTimePerMove(final int newMaxComputationTimePerMove) {
87: if (newMaxComputationTimePerMove <= 0) {
88: throw new IllegalArgumentException("The computation time must be positive.");
89: }
90: this.maxComputationTimePerMove = newMaxComputationTimePerMove;
91: return this;
92: }
93:
94: @Override
95: public IC4PlayerBuilder createPlayerBuilder() {
96: final int playerId = currentPlayerId.incrementAndGet();
97: final C4PlayerBuilder builder = new C4PlayerBuilder(playerId);
98: // store player builder to be able to inject the strategy into the player object
99: // after instantiation
100: this.playerBuilders.put(playerId, builder);
101: return builder;
102: }
103:
104: @Override
105: public IC4GameBuilder addPlayer(final IC4Player player, final IC4Strategy strategy) throws GameException {
106: Objects.requireNonNull(player, "player");
107: Objects.requireNonNull(strategy, "strategy");
108: final C4PlayerBuilder builder = this.playerBuilders.get(player.getToken());
109: if (builder == null) {
110: throw new GameException(String.format("Encountered player %s with invalid or expired token: %s",
111: player, player.getToken()));
112: }
113: for (final var p : this.players) {
114: if (player.getName().equalsIgnoreCase(p.getName())) {
115: throw new GameException(String.format("Tried to add two players with identical name: %s",
116: player.getName()));
117: }
118: }
119: // we need a way to store the strategy in the player instance (because that's where it belongs)
120: // that is handled via an internal callback set in the ctor of the player.
121: // We don't want client code to be able to modify strategies, so no simple public setters :)
122: builder.injectPlayerStrategyUsingHook(strategy);
123: this.playerBuilders.remove(player.getToken());
124: this.players.add(player);
125: return this;
126: }
127:
128: @Override
129: public IC4GameBuilder changeBoardRows(final int newRowCount) {
130: this.rowCount = newRowCount;
131: return this;
132: }
133:
134: @Override
135: public IC4GameBuilder changeBoardColumns(final int newColumnCount) {
136: this.columnCount = newColumnCount;
137: return this;
138: }
139:
140: @Override
141: public IC4GameBuilder changeRequiredSolutionSize(final int newSolutionSize) {
142: this.solutionSize = newSolutionSize;
143: return this;
144: }
145:
146: @Override
147: public IC4GameBuilder changeObserverFactoryProvider(final ObserverFactoryProvider newObserverFactoryProvider) {
148: this.observerFactoryProvider = newObserverFactoryProvider;
149: return this;
150: }
151:
152: @Override
153: public IC4Game build(final int id) throws GameException, InterruptedException {
154: // it's questionable how exciting a Connect Four game with a single player would be, but its fine by me
155: // who am I to judge ¯\_(ツ)_/¯
156:• if (this.players.isEmpty()) {
157: throw new GameException("A Connect Four game needs players to..., you know..., qualify as a game :P");
158: }
159: // ensure players are ordered.
160: Collections.sort(this.players, (p1, p2) -> p1.getToken() - p2.getToken());
161: // now assert player IDs are consecutive, unique, and start at 1
162: // we do the same check in the C4State ctor, but better be safe than sorry
163: // at the same time:
164: // - create the token -> player Look Up Table (LUT) for the internal board
165: // - create the name -> strategy LUT for the gaming framework
166: final IC4Player[] playerArr = new IC4Player[this.players.size()];
167: this.players.toArray(playerArr);
168: final Map<Integer, IC4Player> playerTokenLut = new HashMap<>(playerArr.length);
169: final Map<String, IC4Strategy> strategyLut = new HashMap<>(playerArr.length);
170:• for (int i = 0; i < playerArr.length; i++) {
171: final IC4Player player = playerArr[i];
172:• if (player.getToken() != i + 1) {
173: throw new IllegalStateException(String.format("The 'players' must be ordered ascending by tokens, "
174: + "starting at token 1 and incrementing by 1 for each following player!. Player %s at index %s "
175: + "violates this rule!",
176: player, i));
177: }
178: playerTokenLut.put(player.getToken(), player);
179: strategyLut.put(player.getName(), player.getStrategy());
180: }
181: final IReadOnlyDictionary<Integer, IC4Player> roPlayerTokenLut = ReadOnlyDictionary.of(playerTokenLut);
182: final IC4BoardSlim slimBoard = new C4BoardSlim(roPlayerTokenLut, this.rowCount, this.columnCount,
183: this.solutionSize);
184: final IC4Board board = new C4Board(slimBoard);
185: final IC4State initialState = new C4State(playerArr, board);
186: return new C4Game(id, initialState, strategyLut, this.maxComputationTimePerMove,
187: // some black magic going on here, but it's fine, or so I'm told
188: // no idea how Java manages to type cast a boolean member of a completely
189: // unrelated class to an IMoveChecker instance, but apparently it does
190: // ¯\_(ツ)_/¯
191: // (why am I even using Java again?)
192: // feel free to enlighten me if you know what's going on here :P
193: IC4Move.class::isInstance,
194: observerFactoryProvider);
195: }
196: }