Skip to contentMethod: getStrategy(Map)
      1: /*
2:  * Copyright © 2020-2023 Fachhochschule für die Wirtschaft (FHDW) Hannover
3:  *
4:  * This file is part of othello-core.
5:  *
6:  * Othello-core is free software: you can redistribute it and/or modify
7:  * it under the terms of the GNU General Public License as published by
8:  * the Free Software Foundation, either version 3 of the License, or
9:  * (at your option) any later version.
10:  *
11:  * Othello-core is distributed in the hope that it will be useful,
12:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14:  * GNU General Public License for more details.
15:  *
16:  * You should have received a copy of the GNU General Public License
17:  * along with othello-core.  If not, see <http://www.gnu.org/licenses/>.
18:  */
19: package de.fhdw.gaming.othello.core.domain.impl;
20: 
21: import java.util.ArrayList;
22: import java.util.LinkedHashSet;
23: import java.util.List;
24: import java.util.Map;
25: import java.util.Optional;
26: import java.util.Set;
27: import java.util.regex.Pattern;
28: 
29: import de.fhdw.gaming.core.domain.GameBuilder;
30: import de.fhdw.gaming.core.domain.GameBuilderFactory;
31: import de.fhdw.gaming.core.domain.GameException;
32: import de.fhdw.gaming.core.domain.Strategy;
33: import de.fhdw.gaming.core.ui.InputProvider;
34: import de.fhdw.gaming.core.ui.InputProviderException;
35: import de.fhdw.gaming.core.ui.type.validator.MaxValueValidator;
36: import de.fhdw.gaming.core.ui.type.validator.MinValueValidator;
37: import de.fhdw.gaming.core.ui.type.validator.PatternValidator;
38: import de.fhdw.gaming.core.ui.type.validator.Validator;
39: import de.fhdw.gaming.othello.core.domain.OthelloGameBuilder;
40: import de.fhdw.gaming.othello.core.domain.OthelloGameBuilderFactory;
41: import de.fhdw.gaming.othello.core.domain.OthelloPlayer;
42: import de.fhdw.gaming.othello.core.domain.OthelloPlayerBuilder;
43: import de.fhdw.gaming.othello.core.domain.OthelloStrategy;
44: import de.fhdw.gaming.othello.core.domain.factory.OthelloDefaultStrategyFactoryProvider;
45: import de.fhdw.gaming.othello.core.domain.factory.OthelloStrategyFactory;
46: import de.fhdw.gaming.othello.core.domain.factory.OthelloStrategyFactoryProvider;
47: import de.fhdw.gaming.othello.core.moves.factory.OthelloMoveFactory;
48: import de.fhdw.gaming.othello.core.moves.impl.OthelloDefaultMoveFactory;
49: 
50: /**
51:  * Implements {@link GameBuilderFactory} by creating an Othello game builder.
52:  */
53: public final class OthelloGameBuilderFactoryImpl implements OthelloGameBuilderFactory {
54: 
55:     /**
56:      * The number of players.
57:      */
58:     private static final int NUMBER_OF_PLAYERS = 2;
59:     /**
60:      * Minimum number of rows (and columns) of the board.
61:      */
62:     private static final int MIN_BOARD_SIZE = 4;
63:     /**
64:      * Maximum number of rows (and columns) of the board.
65:      */
66:     private static final int MAX_BOARD_SIZE = 16;
67:     /**
68:      * Smallest allowed maximum computation time per move in seconds.
69:      */
70:     private static final int MIN_MAX_COMPUTATION_TIME_PER_MOVE = 1;
71:     /**
72:      * Largest allowed maximum computation time per move in seconds.
73:      */
74:     private static final int MAX_MAX_COMPUTATION_TIME_PER_MOVE = 3600;
75: 
76:     /**
77:      * All available Othello strategies.
78:      */
79:     private final Set<OthelloStrategy> strategies;
80: 
81:     /**
82:      * Creates an Othello game factory. Othello strategies are loaded by using the {@link java.util.ServiceLoader}.
83:      * <p>
84:      * This constructor is meant to be used by the {@link java.util.ServiceLoader}.
85:      */
86:     public OthelloGameBuilderFactoryImpl() {
87:         this(new OthelloDefaultStrategyFactoryProvider());
88:     }
89: 
90:     /**
91:      * Creates an Othello game factory.
92:      *
93:      * @param strategyFactoryProvider The {@link OthelloStrategyFactoryProvider} for loading Othello strategies.
94:      */
95:     OthelloGameBuilderFactoryImpl(final OthelloStrategyFactoryProvider strategyFactoryProvider) {
96:         final OthelloMoveFactory moveFactory = new OthelloDefaultMoveFactory();
97: 
98:         final List<OthelloStrategyFactory> factories = strategyFactoryProvider.getStrategyFactories();
99:         this.strategies = new LinkedHashSet<>();
100:         for (final OthelloStrategyFactory factory : factories) {
101:             this.strategies.add(factory.create(moveFactory));
102:         }
103:     }
104: 
105:     @Override
106:     public String getName() {
107:         return "Othello";
108:     }
109: 
110:     @Override
111:     public int getMinimumNumberOfPlayers() {
112:         return OthelloGameBuilderFactoryImpl.NUMBER_OF_PLAYERS;
113:     }
114: 
115:     @Override
116:     public int getMaximumNumberOfPlayers() {
117:         return OthelloGameBuilderFactoryImpl.NUMBER_OF_PLAYERS;
118:     }
119: 
120:     @Override
121:     public List<? extends Strategy<?, ?, ?>> getStrategies() {
122:         return new ArrayList<>(this.strategies);
123:     }
124: 
125:     @Override
126:     public OthelloGameBuilder createGameBuilder(final InputProvider inputProvider) throws GameException {
127:         try {
128:             final OthelloGameBuilder gameBuilder = new OthelloGameBuilderImpl();
129: 
130:             @SuppressWarnings("unchecked")
131:             final Map<String,
132:                     Object> gameData = inputProvider.needInteger(
133:                             OthelloGameBuilderFactory.PARAM_BOARD_SIZE,
134:                             "Number of rows (and columns)",
135:                             Optional.of(OthelloGameBuilder.DEFAULT_BOARD_SIZE),
136:                             new MinValueValidator<>(OthelloGameBuilderFactoryImpl.MIN_BOARD_SIZE),
137:                             new MaxValueValidator<>(OthelloGameBuilderFactoryImpl.MAX_BOARD_SIZE),
138:                             new Validator<>() {
139: 
140:                                 @Override
141:                                 public Integer validate(final Integer value) throws InputProviderException {
142:                                     if (value % 2 != 0) {
143:                                         throw new InputProviderException(this.getInfo());
144:                                     } else {
145:                                         return value;
146:                                     }
147:                                 }
148: 
149:                                 @Override
150:                                 public String getInfo() {
151:                                     return "The value must be an even number.";
152:                                 }
153:                             })
154:                             .needInteger(
155:                                     GameBuilderFactory.PARAM_MAX_COMPUTATION_TIME_PER_MOVE,
156:                                     "Maximum computation time per move in seconds",
157:                                     Optional.of(GameBuilder.DEFAULT_MAX_COMPUTATION_TIME_PER_MOVE),
158:                                     new MinValueValidator<>(
159:                                             OthelloGameBuilderFactoryImpl.MIN_MAX_COMPUTATION_TIME_PER_MOVE),
160:                                     new MaxValueValidator<>(
161:                                             OthelloGameBuilderFactoryImpl.MAX_MAX_COMPUTATION_TIME_PER_MOVE))
162:                             .requestData("Board properties");
163: 
164:             gameBuilder.changeBoardSize((Integer) gameData.get(OthelloGameBuilderFactory.PARAM_BOARD_SIZE));
165:             gameBuilder.changeMaximumComputationTimePerMove(
166:                     (Integer) gameData.get(GameBuilderFactory.PARAM_MAX_COMPUTATION_TIME_PER_MOVE));
167: 
168:             final InputProvider firstPlayerInputProvider = inputProvider.getNext(gameData);
169:             final Map<String, Object> firstPlayerData = this
170:                     .requestPlayerData(firstPlayerInputProvider, "Player 1", Optional.empty());
171:             final OthelloPlayer firstPlayerBuilder = this
172:                     .createPlayer(gameBuilder.createPlayerBuilder(), firstPlayerData);
173:             final OthelloStrategy firstPlayerStrategy = this.getStrategy(firstPlayerData);
174:             gameBuilder.addPlayer(firstPlayerBuilder, firstPlayerStrategy);
175: 
176:             final InputProvider secondPlayerInputProvider = firstPlayerInputProvider.getNext(firstPlayerData);
177:             final Map<String, Object> secondPlayerData = this.requestPlayerData(
178:                     secondPlayerInputProvider,
179:                     "Player 2",
180:                     Optional.of(
181:                             !(Boolean) firstPlayerData.get(OthelloGameBuilderFactory.PARAM_PLAYER_USING_BLACK_TOKENS)));
182:             final OthelloPlayer secondPlayerBuilder = this
183:                     .createPlayer(gameBuilder.createPlayerBuilder(), secondPlayerData);
184:             final OthelloStrategy secondPlayerStrategy = this.getStrategy(secondPlayerData);
185:             gameBuilder.addPlayer(secondPlayerBuilder, secondPlayerStrategy);
186: 
187:             return gameBuilder;
188:         } catch (final InputProviderException e) {
189:             throw new GameException(String.format("Creating Othello game was aborted: %s", e.getMessage()), e);
190:         }
191:     }
192: 
193:     /**
194:      * Initialises an Othello player builder.
195:      *
196:      * @param inputProvider    The input provider.
197:      * @param title            The title for the UI.
198:      * @param usingBlackTokens If set, determines the mandatory colour of the player. Otherwise, the colour is initially
199:      *                         set to black, and the user can change it at will.
200:      * @return {@code playerBuilder}.
201:      * @throws InputProviderException if the operation has been aborted prematurely (e.g. if the user cancelled a
202:      *                                dialog).
203:      */
204:     @SuppressWarnings("unchecked")
205:     private Map<String, Object> requestPlayerData(final InputProvider inputProvider, final String title,
206:             final Optional<Boolean> usingBlackTokens) throws InputProviderException {
207: 
208:         inputProvider
209:                 .needString(
210:                         GameBuilderFactory.PARAM_PLAYER_NAME,
211:                         "Name",
212:                         Optional.empty(),
213:                         new PatternValidator(Pattern.compile("\\S+(\\s+\\S+)*")))
214:                 .needBoolean(
215:                         OthelloGameBuilderFactory.PARAM_PLAYER_USING_BLACK_TOKENS,
216:                         "Black colour",
217:                         Optional.of(Boolean.TRUE))
218:                 .needObject(GameBuilderFactory.PARAM_PLAYER_STRATEGY, "Strategy", Optional.empty(), this.strategies);
219: 
220:         if (usingBlackTokens.isPresent()) {
221:             inputProvider
222:                     .fixedBoolean(OthelloGameBuilderFactory.PARAM_PLAYER_USING_BLACK_TOKENS, usingBlackTokens.get());
223:         }
224: 
225:         return inputProvider.requestData(title);
226:     }
227: 
228:     /**
229:      * Creates an Othello player.
230:      *
231:      * @param playerBuilder The player builder.
232:      * @param playerData    The requested player data.
233:      * @return The created {@link OthelloPlayer}.
234:      */
235:     private OthelloPlayer createPlayer(final OthelloPlayerBuilder playerBuilder, final Map<String, Object> playerData)
236:             throws GameException {
237: 
238:         return playerBuilder.changeName((String) playerData.get(GameBuilderFactory.PARAM_PLAYER_NAME))
239:                 .changeUsingBlackTokens(
240:                         (Boolean) playerData.get(OthelloGameBuilderFactory.PARAM_PLAYER_USING_BLACK_TOKENS))
241:                 .build();
242:     }
243: 
244:     /**
245:      * Returns an Othello strategy.
246:      *
247:      * @param playerData The requested player data.
248:      * @return The Othello strategy.
249:      */
250:     private OthelloStrategy getStrategy(final Map<String, Object> playerData) {
251:         return (OthelloStrategy) playerData.get(GameBuilderFactory.PARAM_PLAYER_STRATEGY);
252:     }
253: }