Skip to contentMethod: changeObserverFactoryProvider(ObserverFactoryProvider)
      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: }