Back to project page it.alcacoop.fourinaline.
The source code is released under:
GNU General Public License
If you think the Android project it.alcacoop.fourinaline listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package org.gojul.fourinaline.model; // w ww . ja v a2s.com import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Random; import java.util.WeakHashMap; import org.gojul.fourinaline.model.GameModel.GameStatus; import org.gojul.fourinaline.model.GameModel.PlayerMark; /** * An implementation of the alpha-beta algorithm for our purpose. * This implementation makes it possible to use any user-developed evaluation * algorithm that can be better than the one provided.<br/> * This algorithm implements a caching mechanism to improve the performance * of the AI player. * * @author Julien Aubin */ public class AlphaBeta implements Serializable { /** * The serial version UID. */ final static long serialVersionUID = 1; /** * The cache initial capacity. */ private final static int CACHE_INITIAL_CAPACITY = 5000; /** * The random which determines which column is to be played * when two columns have the same score. */ private final static Random random = new SecureRandom(); /** * The evaluation function. */ private EvalScore evalScore; /** * The search deepness. */ private int deepness; /** * AI noise, one per level */ private float[] aiNoise = { 2f, 1.3f, 1.0f, 0.55f, 0f }; private int aiLevel; public static double[] ratings = { 650, 900, 1500, 1700, 1900 }; /** * The random factor. */ private float randFactor; /** * The score cache. */ private transient Map<String, Integer> scoreCache; /** * Constructor. * * @param evalScoreFunction * the evaluation function used. * @param deepnessSearch * the search deepness. * @param randomFactor * the random factor used when two possible plays * have the same score. * @throws NullPointerException * if any of the method parameter is null. * @throws IllegalArgumentException * if <code>deepnessSearch</code> is * inferior or equal to 0, or if <code>randomFactor</code> is not in the * [0, 1] range. */ public AlphaBeta(final EvalScore evalScoreFunction, final int deepnessSearch, final float randomFactor, final int aiNoiseLevel) throws NullPointerException, IllegalArgumentException { if (evalScoreFunction == null) throw new NullPointerException(); if (deepnessSearch <= 0) throw new IllegalArgumentException("deepnessSearch"); if (randomFactor < 0.0f || randomFactor > 1.0f) throw new IllegalArgumentException("randomFactor"); evalScore = evalScoreFunction; deepness = deepnessSearch; randFactor = randomFactor; aiLevel = aiNoiseLevel; scoreCache = new WeakHashMap<String, Integer>(CACHE_INITIAL_CAPACITY); } /** * Returns the index of the column to play, or -1 if there's no * more playable column. * * @param gameModel * the game model to consider. * @param playerMark * the player mark to consider. * @return the index of the column to play, or -1 if there's no * more playable column. */ public int getColumnIndex(final GameModel gameModel, final PlayerMark playerMark) { Collection<Integer> possiblePlays = gameModel.getListOfPlayableColumns(); int bestColumn = -1; int bestScore = -Integer.MAX_VALUE; GameModel tempModel = new GameModel(gameModel); // We iterate over the columns from the center // as this is the most interesting order for us. // This quirk improves greatly speed as the best // scores of the alpha beta algorithm are in // the middle columns. List<Integer> playOrder = new ArrayList<Integer>(); int column = (tempModel.getColCount() - 1) / 2; for (int i = 1, len = tempModel.getColCount(); i <= len; i++) { playOrder.add(column); column += (i % 2 == 1) ? i : -i; } // for (int i=0; i<tempModel.getColCount();i++) // playOrder.add(i); List<Integer> iterationOrder = new ArrayList<Integer>(playOrder); iterationOrder.retainAll(possiblePlays); HashMap<Integer, Integer> m = new HashMap<Integer, Integer>(); List<Integer> scoresList = new ArrayList<Integer>(); for (Integer colIndex : iterationOrder) { tempModel.play(colIndex.intValue(), playerMark); String key = tempModel.toUniqueKey(); int currentScore = 0; Integer currentScoreInt = scoreCache.get(key); if (currentScoreInt != null) { currentScore = currentScoreInt.intValue(); } else { // We build the key before performing the alpha-beta evaluation // becuase tempModel is mutable. currentScore = alphaBeta(playOrder, tempModel, playerMark, Integer.MIN_VALUE, -bestScore, 0); scoreCache.put(key, Integer.valueOf(currentScore)); } m.put(colIndex, currentScore); System.out.println("SCORE " + colIndex + ": " + currentScore); tempModel.cancelLastPlay(); if (currentScore > bestScore) { bestScore = currentScore; bestColumn = colIndex; } else if (currentScore == bestScore) { if (random.nextFloat() >= randFactor) { bestColumn = colIndex; } } } System.out.println("OLD BEST COLUMN: " + bestColumn); // Taglio via i piu bassi Iterator<Entry<Integer, Integer>> it = m.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Integer, Integer> pairs = (Map.Entry<Integer, Integer>)it.next(); if ((Integer)pairs.getValue() < -1000) it.remove(); else scoresList.add((Integer)pairs.getValue()); } if ((m.size() == 0) || aiLevel == 5) return bestColumn; // Ordinamento Collections.sort(scoresList); // Max/min int max = scoresList.get(scoresList.size() - 1); int min = scoresList.get(0); float window = Math.abs(max - min); float unit = window / m.size(); // Soglia al di sotto della quale scartare i valori float threshold = aiNoise[aiLevel - 1] * unit; float minToPick = max - threshold; // System.out.println("MINTOPICK:" + minToPick); // Elimino i valori al di sotto di minToPick for (Iterator<Integer> iter = scoresList.iterator(); iter.hasNext();) { Integer score = (Integer)iter.next(); if (score < minToPick) { iter.remove(); } } // Scelgo random tra quelli rimasti Integer value = scoresList.get(random.nextInt(scoresList.size())); // Prendo colIndex dalla mappa Entry<Integer, Integer> item = null; for (Entry<Integer, Integer> entry : m.entrySet()) { if (entry.getValue() == value) { item = entry; break; } } bestColumn = item.getKey(); System.out.println("NEW BEST COLUMN:" + bestColumn); return bestColumn; } /** * Deserializes the AI game client in case of serialization. * * @param in * the input stream responsible of deserialization. * @throws IOException * if an I/O error occurs while deserializing. * @throws ClassNotFoundException * in case a class to be deserialized * is not found. */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); scoreCache = new WeakHashMap<String, Integer>(CACHE_INITIAL_CAPACITY); } /** * Performs an alpha-beta algorithm over the game model <code>gameModel</code>, * with current player <code>playerMark</code>. * * @param playOrder * the play order in which we iterate over the board. * @param gameModel * the game model to consider. * @param playerMark * the player mark to consider. * @param alpha * the alpha value. * @param beta * the beta value. * @param currentDeepness * the deepness in the alpha-beta tree. * @return the score of each possibility of the alpha beta model. */ private int alphaBeta(final List<Integer> playOrder, final GameModel gameModel, final PlayerMark playerMark, final int alpha, final int beta, final int currentDeepness) { // Game won by the player. if (gameModel.getGameStatus() == GameStatus.WON_STATUS) { return Integer.MAX_VALUE - currentDeepness; } // Tie game. else if (gameModel.getGameStatus() == GameStatus.TIE_STATUS) return 0; // Maximum deepness. else if (currentDeepness >= deepness) return evalScore.evaluate(gameModel, playerMark); else { int bestScore = Integer.MIN_VALUE; PlayerMark tempMark = PlayerMark.getNextMark(playerMark); int alphaEval = alpha; Collection<Integer> possiblePlays = gameModel.getListOfPlayableColumns(); List<Integer> iterationOrder = new ArrayList<Integer>(playOrder); iterationOrder.retainAll(possiblePlays); for (Integer colIndex : iterationOrder) { // We avoid there multiple copies of the game model // which are unuseful in our case... gameModel.play(colIndex.intValue(), tempMark); // We cannot use the cache there since it would bring // erroneous results. int currentScore = alphaBeta(playOrder, gameModel, tempMark, -beta, -alphaEval, currentDeepness + 1); gameModel.cancelLastPlay(); if (currentScore > bestScore) { bestScore = currentScore; if (bestScore > alphaEval) { alphaEval = bestScore; if (alphaEval > beta) { // What is good for the other player is bad for this one. return -bestScore; } } } } // What is good for the other player is bad for this one. return -bestScore; } } }