├── AI ├── .gitignore └── src │ ├── search │ ├── flat │ │ ├── HeuristicSampleAdaptedUtils │ │ │ └── HeuristicProportionViewInterface.java │ │ └── OnePlyNoHeuristic.java │ ├── mcts │ │ ├── backpropagation │ │ │ ├── MonteCarloBackprop.java │ │ │ ├── HeuristicBackprop.java │ │ │ ├── AlphaGoBackprop.java │ │ │ └── QualitativeBonus.java │ │ ├── nodes │ │ │ └── StandardNode.java │ │ ├── finalmoveselection │ │ │ ├── FinalMoveSelectionStrategy.java │ │ │ ├── ProportionalExpVisitCount.java │ │ │ ├── MaxAvgScore.java │ │ │ └── RobustChild.java │ │ ├── selection │ │ │ ├── SelectionStrategy.java │ │ │ ├── UCB1.java │ │ │ ├── AG0Selection.java │ │ │ ├── ProgressiveBias.java │ │ │ ├── McBRAVE.java │ │ │ ├── UCB1Tuned.java │ │ │ ├── NoisyAG0Selection.java │ │ │ └── ExItSelection.java │ │ └── playout │ │ │ ├── RandomPlayout.java │ │ │ └── PlayoutStrategy.java │ └── minimax │ │ └── NaiveActionBasedSelection.java │ ├── training │ ├── expert_iteration │ │ ├── params │ │ │ ├── GameParams.java │ │ │ ├── OptimisersParams.java │ │ │ ├── OutParams.java │ │ │ ├── AgentsParams.java │ │ │ ├── FeatureDiscoveryParams.java │ │ │ ├── ObjectiveParams.java │ │ │ └── TrainingParams.java │ │ ├── ExpertPolicy.java │ │ └── menageries │ │ │ ├── NaiveSelfPlay.java │ │ │ └── Menagerie.java │ └── ExperienceSample.java │ ├── utils │ ├── data_structures │ │ ├── ScoredIndex.java │ │ ├── support │ │ │ └── zhang_shasha │ │ │ │ ├── Node.java │ │ │ │ └── Main.java │ │ ├── ScoredMove.java │ │ ├── experience_buffers │ │ │ └── ExperienceBuffer.java │ │ └── ludeme_trees │ │ │ └── LudemeTreeUtils.java │ ├── DoNothingAI.java │ ├── RandomAI.java │ ├── ExponentialMovingAverage.java │ ├── LudiiAI.java │ └── analysis │ │ └── BestBaseAgents.java │ ├── policies │ ├── softmax │ │ └── SoftmaxPolicy.java │ └── Policy.java │ ├── decision_trees │ ├── classifiers │ │ ├── BinaryLeafNode.java │ │ ├── DecisionLeafNode.java │ │ ├── DecisionTreeNode.java │ │ └── DecisionConditionNode.java │ └── logits │ │ ├── LogitModelNode.java │ │ ├── LogitDecisionNode.java │ │ └── LogitTreeNode.java │ ├── playout_move_selectors │ ├── EpsilonGreedyWrapper.java │ ├── FeaturesSoftmaxMoveSelector.java │ ├── DecisionTreeMoveSelector.java │ └── LogitTreeMoveSelector.java │ ├── optimisers │ ├── Optimiser.java │ ├── OptimiserFactory.java │ └── SGD.java │ └── function_approx │ └── LinearFunction.java ├── Features ├── .gitignore └── src │ └── features │ ├── aspatial │ ├── AspatialFeature.java │ ├── InterceptFeature.java │ ├── PassMoveFeature.java │ └── SwapMoveFeature.java │ ├── spatial │ ├── FeatureUtils.java │ ├── cache │ │ ├── BaseCachedData.java │ │ └── footprints │ │ │ ├── BaseFootprint.java │ │ │ └── FullFootprint.java │ ├── graph_search │ │ ├── Path.java │ │ └── GraphSearch.java │ └── instances │ │ ├── BitwiseTest.java │ │ ├── OneOfMustEmpty.java │ │ ├── OneOfMustWho.java │ │ └── OneOfMustWhat.java │ ├── Feature.java │ ├── feature_sets │ └── network │ │ ├── PropFeatureInstanceSet.java │ │ ├── PropNode.java │ │ ├── FeaturePropNode.java │ │ └── Conjunction.java │ ├── WeightVector.java │ └── FeatureVector.java ├── .gitignore ├── resources ├── LOGO_ERC-FLAG_EU_.jpg └── ludii-logo-64x64.png └── LICENSE /AI/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | *.iml 3 | /target/ 4 | -------------------------------------------------------------------------------- /Features/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | *.iml 3 | /target/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | */.settings/* 2 | */bin/ 3 | */resources/ 4 | */.classpath 5 | */build.xml 6 | */.project -------------------------------------------------------------------------------- /resources/LOGO_ERC-FLAG_EU_.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ludeme/LudiiAI/HEAD/resources/LOGO_ERC-FLAG_EU_.jpg -------------------------------------------------------------------------------- /resources/ludii-logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ludeme/LudiiAI/HEAD/resources/ludii-logo-64x64.png -------------------------------------------------------------------------------- /AI/src/search/flat/HeuristicSampleAdaptedUtils/HeuristicProportionViewInterface.java: -------------------------------------------------------------------------------- 1 | package search.flat.HeuristicSampleAdaptedUtils; 2 | 3 | import game.Game; 4 | import other.context.Context; 5 | import search.flat.HeuristicSampleAdapted; 6 | import search.flat.HeuristicSampleAdapted.MoveHeuristicEvaluation; 7 | 8 | /** 9 | * used to allow classes of modul "distance" to access 10 | * @author Markus 11 | * 12 | */ 13 | public interface HeuristicProportionViewInterface { 14 | 15 | void addObserver(HeuristicSampleAdapted heuristicSampleAdapted); 16 | 17 | void update(MoveHeuristicEvaluation latestMoveHeuristicEvaluation, Game game, Context context); 18 | 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /AI/src/search/mcts/backpropagation/MonteCarloBackprop.java: -------------------------------------------------------------------------------- 1 | package search.mcts.backpropagation; 2 | 3 | import other.context.Context; 4 | import search.mcts.MCTS; 5 | import search.mcts.nodes.BaseNode; 6 | 7 | /** 8 | * Standard backpropagation implementation for MCTS, performing Monte-Carlo backups 9 | * of playout outcomes. 10 | * 11 | * @author Dennis Soemers 12 | */ 13 | public class MonteCarloBackprop extends BackpropagationStrategy 14 | { 15 | 16 | @Override 17 | public void computeUtilities 18 | ( 19 | final MCTS mcts, 20 | final BaseNode startNode, 21 | final Context context, 22 | final double[] utilities, 23 | final int numPlayoutMoves 24 | ) 25 | { 26 | // Do nothing 27 | } 28 | 29 | @Override 30 | public int backpropagationFlags() 31 | { 32 | return 0; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/params/GameParams.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.params; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Wrapper around params for game setup/configuration in training runs. 7 | * 8 | * @author Dennis Soemers 9 | */ 10 | public class GameParams 11 | { 12 | 13 | //------------------------------------------------------------------------- 14 | 15 | /** Name of the game to play. Should end with .lud */ 16 | public String gameName; 17 | 18 | /** List of game options to use when compiling game */ 19 | public List gameOptions; 20 | 21 | /** Name of ruleset to compile. Any options will be ignored if ruleset is provided. */ 22 | public String ruleset; 23 | 24 | /** Maximum game duration (in moves) */ 25 | public int gameLengthCap; 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | } 30 | -------------------------------------------------------------------------------- /AI/src/utils/data_structures/ScoredIndex.java: -------------------------------------------------------------------------------- 1 | package utils.data_structures; 2 | 3 | /** 4 | * 5 | * Simple wrapper for score + index, used for sorting indices (usually move indices) based on scores. 6 | * Almost identical to ScoredMove. 7 | * 8 | * @author cyprien 9 | * 10 | */ 11 | public class ScoredIndex implements Comparable 12 | { 13 | 14 | /** The move */ 15 | public final int index; 16 | 17 | /** The move's score */ 18 | public final float score; 19 | 20 | /** 21 | * Constructor 22 | * @param score 23 | */ 24 | public ScoredIndex(final int index, final float score) 25 | { 26 | this.index = index; 27 | this.score = score; 28 | } 29 | 30 | @Override 31 | public int compareTo(final ScoredIndex other) 32 | { 33 | final float delta = other.score - score; 34 | if (delta < 0.f) 35 | return -1; 36 | else if (delta > 0.f) 37 | return 1; 38 | else 39 | return 0; 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /Features/src/features/aspatial/AspatialFeature.java: -------------------------------------------------------------------------------- 1 | package features.aspatial; 2 | 3 | import features.Feature; 4 | import other.move.Move; 5 | import other.state.State; 6 | 7 | /** 8 | * An aspatial (i.e., not spatial) state-action feature. These features are not 9 | * necessarily binary, i.e. they can return floats. 10 | * 11 | * @author Dennis Soemers 12 | */ 13 | public abstract class AspatialFeature extends Feature 14 | { 15 | 16 | //------------------------------------------------------------------------- 17 | 18 | /** 19 | * @param state 20 | * @param move 21 | * @return Fetaure value for given move in given state. 22 | */ 23 | public abstract float featureVal(final State state, final Move move); 24 | 25 | //------------------------------------------------------------------------- 26 | 27 | @Override 28 | public String toString() 29 | { 30 | throw new UnsupportedOperationException(); 31 | } 32 | 33 | //------------------------------------------------------------------------- 34 | 35 | } 36 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/params/OptimisersParams.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.params; 2 | 3 | /** 4 | * Wrapper around params for optimisers in training runs. 5 | * 6 | * @author Dennis Soemers 7 | */ 8 | public class OptimisersParams 9 | { 10 | 11 | //------------------------------------------------------------------------- 12 | 13 | /** Optimiser to use when optimising policy for Selection phase */ 14 | public String selectionOptimiserConfig; 15 | 16 | /** Optimiser to use when optimising policy for Playout phase */ 17 | public String playoutOptimiserConfig; 18 | 19 | /** Optimiser to use when optimising the Cross-Entropy Exploration policy */ 20 | public String ceExploreOptimiserConfig; 21 | 22 | /** Optimiser to use when optimising policy on TSPG objective (see CoG 2019 paper) */ 23 | public String tspgOptimiserConfig; 24 | 25 | /** Optimiser to use when optimising value function */ 26 | public String valueOptimiserConfig; 27 | 28 | //------------------------------------------------------------------------- 29 | 30 | } 31 | -------------------------------------------------------------------------------- /AI/src/utils/data_structures/support/zhang_shasha/Node.java: -------------------------------------------------------------------------------- 1 | package utils.data_structures.support.zhang_shasha; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * Code originally from: https://github.com/ijkilchenko/ZhangShasha 7 | * 8 | * Afterwards modified for style / various improvements 9 | * 10 | * @author Dennis Soemers 11 | */ 12 | public class Node 13 | { 14 | 15 | /** Label of this node */ 16 | public String label; 17 | 18 | /** Index of this node for pre-order traversal of tree */ 19 | public int index; 20 | 21 | // note: trees need not be binary 22 | 23 | /** List of children */ 24 | public ArrayList children = new ArrayList(); 25 | 26 | /** Leftmost node in subtree rooted in this node (or this node if it's a leaf) */ 27 | public Node leftmost; // Used by the recursive O(n) leftmost() function 28 | 29 | /** 30 | * Constructor 31 | */ 32 | public Node() 33 | { 34 | // Do nothing 35 | } 36 | 37 | /** 38 | * Constructor 39 | * @param label 40 | */ 41 | public Node(final String label) 42 | { 43 | this.label = label; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /AI/src/policies/softmax/SoftmaxPolicy.java: -------------------------------------------------------------------------------- 1 | package policies.softmax; 2 | 3 | import policies.Policy; 4 | 5 | /** 6 | * Abstract class for softmax policies; policies that compute 7 | * logits for moves, and then pass them through a softmax to 8 | * obtain a probability distribution over moves. 9 | * 10 | * @author Dennis Soemers 11 | */ 12 | public abstract class SoftmaxPolicy extends Policy 13 | { 14 | 15 | //------------------------------------------------------------------------- 16 | 17 | /** Epsilon for epsilon-greedy playouts */ 18 | protected double epsilon = 0.0; 19 | 20 | /** 21 | * If >= 0, we'll only actually use this softmax policy in MCTS play-outs 22 | * for up to this many actions. If a play-out still did not terminate 23 | * after this many play-out actions, we revert to a random play-out 24 | * strategy as fallback 25 | */ 26 | protected int playoutActionLimit = -1; 27 | 28 | /** Auto-end playouts in a draw if they take more turns than this */ 29 | protected int playoutTurnLimit = -1; 30 | 31 | //------------------------------------------------------------------------- 32 | 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Digital Ludeme Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AI/src/utils/data_structures/ScoredMove.java: -------------------------------------------------------------------------------- 1 | package utils.data_structures; 2 | 3 | import other.move.Move; 4 | 5 | /** 6 | * Simple wrapper for score + move, used for sorting moves based on scores. 7 | * Copied from the AB-Code, but put in an external class so that it can be used by Transposition Tables. 8 | * 9 | * @author cyprien 10 | */ 11 | 12 | public class ScoredMove implements Comparable 13 | { 14 | 15 | /** The move */ 16 | public final Move move; 17 | /** The move's score */ 18 | public final float score; 19 | /** The number of times this move was explored */ 20 | public int nbVisits; 21 | 22 | /** 23 | * Constructor 24 | * @param move 25 | * @param score 26 | */ 27 | public ScoredMove(final Move move, final float score, final int nbVisits) 28 | { 29 | this.move = move; 30 | this.score = score; 31 | this.nbVisits = nbVisits; 32 | } 33 | 34 | @Override 35 | public int compareTo(final ScoredMove other) 36 | { 37 | //nbVisits is not taken into account for the moment 38 | final float delta = other.score - score; 39 | if (delta < 0.f) 40 | return -1; 41 | else if (delta > 0.f) 42 | return 1; 43 | else 44 | return 0; 45 | } 46 | } -------------------------------------------------------------------------------- /AI/src/search/mcts/nodes/StandardNode.java: -------------------------------------------------------------------------------- 1 | package search.mcts.nodes; 2 | 3 | import other.context.Context; 4 | import other.move.Move; 5 | import search.mcts.MCTS; 6 | 7 | /** 8 | * Nodes for "standard" MCTS search trees, for deterministic games. 9 | * This node implementation stores a game state in every node, and 10 | * assumes every node has a fixed list of legal actions. 11 | * 12 | * @author Dennis Soemers 13 | */ 14 | public final class StandardNode extends DeterministicNode 15 | { 16 | 17 | //------------------------------------------------------------------------- 18 | 19 | /** 20 | * Constructor 21 | * 22 | * @param mcts 23 | * @param parent 24 | * @param parentMove 25 | * @param parentMoveWithoutConseq 26 | * @param context 27 | */ 28 | public StandardNode 29 | ( 30 | final MCTS mcts, 31 | final BaseNode parent, 32 | final Move parentMove, 33 | final Move parentMoveWithoutConseq, 34 | final Context context 35 | ) 36 | { 37 | super(mcts, parent, parentMove, parentMoveWithoutConseq, context); 38 | } 39 | 40 | //------------------------------------------------------------------------- 41 | 42 | } 43 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/params/OutParams.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.params; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Wrapper around params for output/file writing. 7 | * 8 | * @author Dennis Soemers 9 | */ 10 | public class OutParams 11 | { 12 | 13 | //------------------------------------------------------------------------- 14 | 15 | /** 16 | * When do we want to store checkpoints of trained weights? 17 | * @author Dennis Soemers 18 | */ 19 | public enum CheckpointTypes 20 | { 21 | /** Store checkpoint after N self-play training games */ 22 | Game, 23 | /** Store checkpoint after N weight updates */ 24 | WeightUpdate 25 | } 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | /** Output directory */ 30 | public File outDir; 31 | 32 | /** When do we store checkpoints of trained weights? */ 33 | public CheckpointTypes checkpointType; 34 | 35 | /** Frequency of checkpoint updates */ 36 | public int checkpointFrequency; 37 | 38 | /** If true, we suppress a bunch of log messages to a log file. */ 39 | public boolean noLogging; 40 | 41 | //------------------------------------------------------------------------- 42 | 43 | } 44 | -------------------------------------------------------------------------------- /AI/src/search/mcts/backpropagation/HeuristicBackprop.java: -------------------------------------------------------------------------------- 1 | package search.mcts.backpropagation; 2 | 3 | import other.context.Context; 4 | import search.mcts.MCTS; 5 | import search.mcts.nodes.BaseNode; 6 | import utils.AIUtils; 7 | 8 | /** 9 | * Implementation of backpropagation that uses heuristic value estimates 10 | * for any player that is still active at the end of a playout, instead 11 | * of defaulting to 0.0 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public class HeuristicBackprop extends BackpropagationStrategy 16 | { 17 | 18 | @Override 19 | public void computeUtilities 20 | ( 21 | final MCTS mcts, 22 | final BaseNode startNode, 23 | final Context context, 24 | final double[] utilities, 25 | final int numPlayoutMoves 26 | ) 27 | { 28 | assert (mcts.heuristics() != null); 29 | 30 | if (context.active()) 31 | { 32 | // Playout did not terminate, so should run heuristics at end of playout 33 | final double[] playoutHeuristicValues = AIUtils.heuristicValueEstimates(context, mcts.heuristics()); 34 | for (int p = 1; p < utilities.length; ++p) 35 | { 36 | utilities[p] = playoutHeuristicValues[p]; 37 | } 38 | } 39 | } 40 | 41 | @Override 42 | public int backpropagationFlags() 43 | { 44 | return 0; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/ExpertPolicy.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration; 2 | 3 | import java.util.List; 4 | 5 | import main.collections.FVector; 6 | import main.collections.FastArrayList; 7 | import other.AI; 8 | import other.move.Move; 9 | 10 | /** 11 | * Abstract class for policies that can serve as experts in Expert Iteration 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public abstract class ExpertPolicy extends AI 16 | { 17 | 18 | /** 19 | * @return Should return a list of the moves considered at the 20 | * "root" state during the last search executed by this expert. 21 | */ 22 | public abstract FastArrayList lastSearchRootMoves(); 23 | 24 | /** 25 | * @param tau Temperature parameter that may or may not be used 26 | * by some experts. For MCTS, tau = 1.0 means proportional to 27 | * visit counts, whereas tau --> 0.0 means greedy with respect 28 | * to visit counts. 29 | * @return Policy / distribution over actions as computed by expert 30 | */ 31 | public abstract FVector computeExpertPolicy(final double tau); 32 | 33 | /** 34 | * @return A list of samples of experience for Expert Iteration, based on 35 | * the last search executed by this expert. 36 | */ 37 | public abstract List generateExItExperiences(); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Features/src/features/spatial/FeatureUtils.java: -------------------------------------------------------------------------------- 1 | package features.spatial; 2 | 3 | import other.move.Move; 4 | 5 | /** 6 | * Some utility methods related to features 7 | * 8 | * @author Dennis Soemers 9 | */ 10 | public class FeatureUtils 11 | { 12 | 13 | //------------------------------------------------------------------------- 14 | 15 | /** 16 | * Private constructor, should not use 17 | */ 18 | private FeatureUtils() 19 | { 20 | // should not instantiate 21 | } 22 | 23 | //------------------------------------------------------------------------- 24 | 25 | /** 26 | * @param move 27 | * @return Extracts a from-position from an action 28 | */ 29 | public static int fromPos(final Move move) 30 | { 31 | if (move == null || move.isPass()) 32 | { 33 | return -1; 34 | } 35 | 36 | int fromPos = move.fromNonDecision(); 37 | 38 | if (fromPos == move.toNonDecision()) 39 | { 40 | fromPos = -1; 41 | } 42 | 43 | return fromPos; 44 | } 45 | 46 | /** 47 | * @param move 48 | * @return Extracts a to-position from an action 49 | */ 50 | public static int toPos(final Move move) 51 | { 52 | if (move == null || move.isPass()) 53 | { 54 | return -1; 55 | } 56 | 57 | return move.toNonDecision(); 58 | } 59 | 60 | //------------------------------------------------------------------------- 61 | 62 | } 63 | -------------------------------------------------------------------------------- /AI/src/decision_trees/classifiers/BinaryLeafNode.java: -------------------------------------------------------------------------------- 1 | package decision_trees.classifiers; 2 | 3 | import features.FeatureVector; 4 | 5 | /** 6 | * Leaf node in a feature-based decision tree, with probabilities for classes. 7 | * 8 | * @author Dennis Soemers 9 | */ 10 | public class BinaryLeafNode extends DecisionTreeNode 11 | { 12 | 13 | //------------------------------------------------------------------------- 14 | 15 | /** Predicted probability of being a top move */ 16 | protected final float prob; 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | /** 21 | * Constructor 22 | * @param prob 23 | */ 24 | public BinaryLeafNode 25 | ( 26 | final float prob 27 | ) 28 | { 29 | this.prob = prob; 30 | } 31 | 32 | //------------------------------------------------------------------------- 33 | 34 | @Override 35 | public float predict(final FeatureVector featureVector) 36 | { 37 | return prob; 38 | } 39 | 40 | //------------------------------------------------------------------------- 41 | 42 | @Override 43 | public metadata.ai.features.trees.classifiers.DecisionTreeNode toMetadataNode() 44 | { 45 | return new metadata.ai.features.trees.classifiers.BinaryLeaf(Float.valueOf(prob)); 46 | } 47 | 48 | //------------------------------------------------------------------------- 49 | 50 | } 51 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/params/AgentsParams.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.params; 2 | 3 | /** 4 | * Wrapper around params for agents setup/configuration in training runs. 5 | * 6 | * @author Dennis Soemers 7 | */ 8 | public class AgentsParams 9 | { 10 | 11 | //------------------------------------------------------------------------- 12 | 13 | /** Type of AI to use as expert */ 14 | public String expertAI; 15 | 16 | /** Filepath for best agents data directory for this specific game (+ options) */ 17 | public String bestAgentsDataDir; 18 | 19 | /** Max allowed thinking time per move (in seconds) */ 20 | public double thinkingTime; 21 | 22 | /** Max allowed number of MCTS iterations per move */ 23 | public int iterationLimit; 24 | 25 | /** Search depth limit (for e.g. Alpha-Beta experts) */ 26 | public int depthLimit; 27 | 28 | /** Maximum number of actions per playout which we'll bias using features (-1 for no limit) */ 29 | public int maxNumBiasedPlayoutActions; 30 | 31 | /** If true, use tournament mode (similar to the one in Polygames) */ 32 | public boolean tournamentMode; 33 | 34 | /** Epsilon for epsilon-greedy features-based playouts */ 35 | public double playoutFeaturesEpsilon; 36 | 37 | /** Number of threads to use for Tree Parallelisation in MCTS-based agents */ 38 | public int numAgentThreads; 39 | 40 | //------------------------------------------------------------------------- 41 | 42 | } 43 | -------------------------------------------------------------------------------- /AI/src/utils/DoNothingAI.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import game.Game; 4 | import other.AI; 5 | import other.context.Context; 6 | import other.move.Move; 7 | 8 | /** 9 | * AI player doing nothing. 10 | * 11 | * @author Eric.Piette 12 | */ 13 | public class DoNothingAI extends AI 14 | { 15 | 16 | //------------------------------------------------------------------------- 17 | 18 | /** Our player index */ 19 | protected int player = -1; 20 | 21 | /** The last move we returned */ 22 | protected Move lastReturnedMove = null; 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** 27 | * Constructor 28 | */ 29 | public DoNothingAI() 30 | { 31 | friendlyName = "Do Nothing"; 32 | } 33 | 34 | //------------------------------------------------------------------------- 35 | 36 | @Override 37 | public Move selectAction 38 | ( 39 | final Game game, 40 | final Context context, 41 | final double maxSeconds, 42 | final int maxIterations, 43 | final int maxDepth 44 | ) 45 | { 46 | return null; 47 | } 48 | 49 | /** 50 | * @return The last move we returned 51 | */ 52 | public Move lastReturnedMove() 53 | { 54 | return lastReturnedMove; 55 | } 56 | 57 | @Override 58 | public void initAI(final Game game, final int playerID) 59 | { 60 | this.player = playerID; 61 | lastReturnedMove = null; 62 | } 63 | 64 | //------------------------------------------------------------------------- 65 | 66 | } 67 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/params/FeatureDiscoveryParams.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.params; 2 | 3 | /** 4 | * Wrapper around params for feature discovery settings. 5 | * 6 | * @author Dennis Soemers 7 | */ 8 | public class FeatureDiscoveryParams 9 | { 10 | 11 | //------------------------------------------------------------------------- 12 | 13 | /** After this many training games, we add a new feature. */ 14 | public int addFeatureEvery; 15 | 16 | /** If true, we'll not grow feature set (but still train weights) */ 17 | public boolean noGrowFeatureSet; 18 | 19 | /** At most this number of feature instances will be taken into account when combining features */ 20 | public int combiningFeatureInstanceThreshold; 21 | 22 | /** Number of threads to use for parallel feature discovery */ 23 | public int numFeatureDiscoveryThreads; 24 | 25 | /** Critical value used when computing confidence intervals for correlations */ 26 | public double criticalValueCorrConf; 27 | 28 | /** If true, use a special-moves expander in addition to the normal one */ 29 | public boolean useSpecialMovesExpander; 30 | 31 | /** If true, use a special-moves expander in addition to the normal one, but split time with the normal one (so same number of total features) */ 32 | public boolean useSpecialMovesExpanderSplit; 33 | 34 | /** Type of feature set expander to use */ 35 | public String expanderType; 36 | 37 | //------------------------------------------------------------------------- 38 | 39 | } 40 | -------------------------------------------------------------------------------- /AI/src/utils/data_structures/experience_buffers/ExperienceBuffer.java: -------------------------------------------------------------------------------- 1 | package utils.data_structures.experience_buffers; 2 | 3 | import java.util.List; 4 | 5 | import training.expert_iteration.ExItExperience; 6 | 7 | /** 8 | * Interface for experience buffers. Declares common methods 9 | * that we expect in uniform as well as Prioritized Experience Replay 10 | * buffers. 11 | * 12 | * @author Dennis Soemers 13 | */ 14 | public interface ExperienceBuffer 15 | { 16 | 17 | /** 18 | * Adds a new sample of experience. 19 | * Defaulting to the max observed priority level in the case of PER. 20 | * 21 | * @param experience 22 | */ 23 | public void add(final ExItExperience experience); 24 | 25 | /** 26 | * @param batchSize 27 | * @return A batch of the given batch size, sampled uniformly with 28 | * replacement. 29 | */ 30 | public List sampleExperienceBatch(final int batchSize); 31 | 32 | /** 33 | * @param batchSize 34 | * @return Sample of batchSize tuples of experience, sampled uniformly 35 | */ 36 | public List sampleExperienceBatchUniformly(final int batchSize); 37 | 38 | /** 39 | * @return Return the backing array containing ALL experience (including likely 40 | * null entries if the buffer was not completely filled). 41 | */ 42 | public ExItExperience[] allExperience(); 43 | 44 | /** 45 | * Writes this complete buffer to a binary file 46 | * @param filepath 47 | */ 48 | public void writeToFile(final String filepath); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/params/ObjectiveParams.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.params; 2 | 3 | /** 4 | * Wrapper around params for objective function(s) in training runs. 5 | * 6 | * @author Dennis Soemers 7 | */ 8 | public class ObjectiveParams 9 | { 10 | 11 | //------------------------------------------------------------------------- 12 | 13 | /** If true, we'll train a policy on TSPG objective (see CoG 2019 paper) */ 14 | public boolean trainTSPG; 15 | 16 | /** If true, we'll use importance sampling weights based on episode durations for CE-loss */ 17 | public boolean importanceSamplingEpisodeDurations; 18 | 19 | /** If true, we use Weighted Importance Sampling instead of Ordinary Importance Sampling for any of the above */ 20 | public boolean weightedImportanceSampling; 21 | 22 | /** If true, we don't do any value function learning */ 23 | public boolean noValueLearning; 24 | 25 | /** If true, we handle move aliasing by putting the maximum mass among all aliased moves on each of them, for training selection policy. */ 26 | public boolean handleAliasing; 27 | 28 | /** If true, we handle move aliasing by putting the maximum mass among all aliased moves on each of them, for training playout policy. */ 29 | public boolean handleAliasingPlayouts; 30 | 31 | /** Lambda param for weight decay (~= 2c for L2 regularisation, in absence of momentum) */ 32 | public double weightDecayLambda; 33 | 34 | //------------------------------------------------------------------------- 35 | 36 | } 37 | -------------------------------------------------------------------------------- /AI/src/policies/Policy.java: -------------------------------------------------------------------------------- 1 | package policies; 2 | 3 | import main.collections.FVector; 4 | import main.collections.FastArrayList; 5 | import other.AI; 6 | import other.context.Context; 7 | import other.move.Move; 8 | import search.mcts.playout.PlayoutStrategy; 9 | 10 | /** 11 | * A policy is something that can compute distributions over actions in a given 12 | * state (presumably using some form of function approximation). 13 | * 14 | * Policies should also implement the methods required to function as 15 | * Play-out strategies for MCTS or function as a full AI agent. 16 | * 17 | * @author Dennis Soemers 18 | */ 19 | public abstract class Policy extends AI implements PlayoutStrategy 20 | { 21 | 22 | //------------------------------------------------------------------------- 23 | 24 | /** 25 | * @param context 26 | * @param actions 27 | * @param thresholded 28 | * @return Probability distribution over the given list of actions in the given state. 29 | */ 30 | public abstract FVector computeDistribution 31 | ( 32 | final Context context, 33 | final FastArrayList actions, 34 | final boolean thresholded 35 | ); 36 | 37 | //------------------------------------------------------------------------- 38 | 39 | /** 40 | * @param context 41 | * @param move 42 | * @return Logit for a single move in a single state 43 | */ 44 | public abstract float computeLogit(final Context context, final Move move); 45 | 46 | //------------------------------------------------------------------------- 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Features/src/features/spatial/cache/BaseCachedData.java: -------------------------------------------------------------------------------- 1 | package features.spatial.cache; 2 | 3 | import features.spatial.cache.footprints.BaseFootprint; 4 | import other.state.container.ContainerState; 5 | 6 | /** 7 | * Abstract class for data cached in active-feature-caches 8 | * 9 | * @author Dennis Soemers 10 | */ 11 | public abstract class BaseCachedData 12 | { 13 | 14 | //------------------------------------------------------------------------- 15 | 16 | /** Active features as previously computed and stored in cache */ 17 | protected final int[] activeFeatureIndices; 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** 22 | * Constructor 23 | * @param activeFeatureIndices 24 | */ 25 | public BaseCachedData(final int[] activeFeatureIndices) 26 | { 27 | this.activeFeatureIndices = activeFeatureIndices; 28 | } 29 | 30 | //------------------------------------------------------------------------- 31 | 32 | /** 33 | * @return Array of active feature indices as previously cached 34 | */ 35 | public final int[] cachedActiveFeatureIndices() 36 | { 37 | return activeFeatureIndices; 38 | } 39 | 40 | /** 41 | * @param containerState 42 | * @param footprint 43 | * @return Is this cached data still valid for the given container state and footprint? 44 | */ 45 | public abstract boolean isDataValid(final ContainerState containerState, final BaseFootprint footprint); 46 | 47 | //------------------------------------------------------------------------- 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Features/src/features/aspatial/InterceptFeature.java: -------------------------------------------------------------------------------- 1 | package features.aspatial; 2 | 3 | import game.Game; 4 | import other.move.Move; 5 | import other.state.State; 6 | 7 | /** 8 | * Intercept feature (always a value of 1.0) 9 | * 10 | * @author Dennis Soemers 11 | */ 12 | public class InterceptFeature extends AspatialFeature 13 | { 14 | 15 | //------------------------------------------------------------------------- 16 | 17 | /** The singleton instance */ 18 | private static final InterceptFeature INSTANCE = new InterceptFeature(); 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** 23 | * Private: singleton 24 | */ 25 | private InterceptFeature() 26 | { 27 | // Do nothing 28 | } 29 | 30 | //------------------------------------------------------------------------- 31 | 32 | @Override 33 | public float featureVal(final State state, final Move move) 34 | { 35 | return 1.f; 36 | } 37 | 38 | //------------------------------------------------------------------------- 39 | 40 | @Override 41 | public String toString() 42 | { 43 | return "Intercept"; 44 | } 45 | 46 | //------------------------------------------------------------------------- 47 | 48 | @Override 49 | public String generateTikzCode(final Game game) 50 | { 51 | return "\\node[rectangle,draw{,REL_POS}] ({LABEL}) {Intercept};"; 52 | } 53 | 54 | //------------------------------------------------------------------------- 55 | 56 | /** 57 | * @return The singleton instance 58 | */ 59 | public static InterceptFeature instance() 60 | { 61 | return INSTANCE; 62 | } 63 | 64 | //------------------------------------------------------------------------- 65 | 66 | } 67 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/params/TrainingParams.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.params; 2 | 3 | /** 4 | * Wrapper around params for basic training setup/configuration. 5 | * 6 | * @author Dennis Soemers 7 | */ 8 | public class TrainingParams 9 | { 10 | 11 | //------------------------------------------------------------------------- 12 | 13 | /** Number of training games to run */ 14 | public int numTrainingGames; 15 | 16 | /** Max size of minibatches in training. */ 17 | public int batchSize; 18 | 19 | /** Max size of the experience buffer. */ 20 | public int experienceBufferSize; 21 | 22 | /** After this many moves (decision points) in training games, we update weights. */ 23 | public int updateWeightsEvery; 24 | 25 | /** If true, we'll use prioritized experience replay */ 26 | public boolean prioritizedExperienceReplay; 27 | 28 | /** If not null/empty, will try to find a good value function to start with from this directory */ 29 | public String initValueFuncDir; 30 | 31 | /** Number of epochs to run for policy gradients. */ 32 | public int numPolicyGradientEpochs; 33 | 34 | /** Number of trials to run per epoch for policy gradients */ 35 | public int numTrialsPerPolicyGradientEpoch; 36 | 37 | /** Discount factor gamma for policy gradients */ 38 | public double pgGamma; 39 | 40 | /** Weight for entropy regularisation */ 41 | public double entropyRegWeight; 42 | 43 | /** Number of threads to use for parallel trials for policy gradients */ 44 | public int numPolicyGradientThreads; 45 | 46 | /** After running policy gradients, scale obtained weights by this value */ 47 | public double postPGWeightScalar; 48 | 49 | //------------------------------------------------------------------------- 50 | 51 | } 52 | -------------------------------------------------------------------------------- /AI/src/playout_move_selectors/EpsilonGreedyWrapper.java: -------------------------------------------------------------------------------- 1 | package playout_move_selectors; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import main.collections.FastArrayList; 6 | import other.context.Context; 7 | import other.move.Move; 8 | import other.playout.PlayoutMoveSelector; 9 | 10 | /** 11 | * Epsilon-greedy wrapper around a Playout Move Selector 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public class EpsilonGreedyWrapper extends PlayoutMoveSelector 16 | { 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | /** The wrapped playout move selector */ 21 | protected final PlayoutMoveSelector wrapped; 22 | 23 | /** Probability of picking uniformly at random */ 24 | protected final double epsilon; 25 | 26 | //------------------------------------------------------------------------- 27 | 28 | /** 29 | * Constructor 30 | * @param wrapped 31 | * @param epsilon 32 | */ 33 | public EpsilonGreedyWrapper(final PlayoutMoveSelector wrapped, final double epsilon) 34 | { 35 | this.wrapped = wrapped; 36 | this.epsilon = epsilon; 37 | } 38 | 39 | //------------------------------------------------------------------------- 40 | 41 | @Override 42 | public Move selectMove 43 | ( 44 | final Context context, 45 | final FastArrayList maybeLegalMoves, 46 | final int p, 47 | final IsMoveReallyLegal isMoveReallyLegal 48 | ) 49 | { 50 | return wrapped.selectMove(context, maybeLegalMoves, p, isMoveReallyLegal); 51 | } 52 | 53 | @Override 54 | public boolean wantsPlayUniformRandomMove() 55 | { 56 | return ThreadLocalRandom.current().nextDouble() < epsilon; 57 | } 58 | 59 | //------------------------------------------------------------------------- 60 | 61 | } 62 | -------------------------------------------------------------------------------- /AI/src/search/mcts/finalmoveselection/FinalMoveSelectionStrategy.java: -------------------------------------------------------------------------------- 1 | package search.mcts.finalmoveselection; 2 | 3 | import org.json.JSONObject; 4 | 5 | import other.move.Move; 6 | import search.mcts.MCTS; 7 | import search.mcts.nodes.BaseNode; 8 | 9 | /** 10 | * Interface for different strategies of finally selecting the move to play in the real game 11 | * (after searching finished) 12 | * 13 | * @author Dennis Soemers 14 | * 15 | */ 16 | public interface FinalMoveSelectionStrategy 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** 22 | * Should be implemented to select the move to play in the real game 23 | * 24 | * @param mcts 25 | * @param rootNode 26 | * @return The move. 27 | */ 28 | public Move selectMove(final MCTS mcts, final BaseNode rootNode); 29 | 30 | //------------------------------------------------------------------------- 31 | 32 | /** 33 | * Customise the final move selection strategy based on a list of given string inputs 34 | * 35 | * @param inputs 36 | */ 37 | public void customise(final String[] inputs); 38 | 39 | //------------------------------------------------------------------------- 40 | 41 | /** 42 | * @param json 43 | * @return Final Move Selection strategy constructed from given JSON object 44 | */ 45 | public static FinalMoveSelectionStrategy fromJson(final JSONObject json) 46 | { 47 | FinalMoveSelectionStrategy selection = null; 48 | final String strategy = json.getString("strategy"); 49 | 50 | if (strategy.equalsIgnoreCase("RobustChild")) 51 | { 52 | return new RobustChild(); 53 | } 54 | 55 | return selection; 56 | } 57 | 58 | //------------------------------------------------------------------------- 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Features/src/features/aspatial/PassMoveFeature.java: -------------------------------------------------------------------------------- 1 | package features.aspatial; 2 | 3 | import game.Game; 4 | import other.move.Move; 5 | import other.state.State; 6 | 7 | /** 8 | * Binary feature that has a value of 1.0 for any move that is a pass move. 9 | * 10 | * @author Dennis Soemers 11 | */ 12 | public class PassMoveFeature extends AspatialFeature 13 | { 14 | 15 | //------------------------------------------------------------------------- 16 | 17 | /** The singleton instance */ 18 | private static final PassMoveFeature INSTANCE = new PassMoveFeature(); 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** 23 | * Private: singleton 24 | */ 25 | private PassMoveFeature() 26 | { 27 | // Do nothing 28 | } 29 | 30 | //------------------------------------------------------------------------- 31 | 32 | @Override 33 | public float featureVal(final State state, final Move move) 34 | { 35 | if (move.isPass()) 36 | return 1.f; 37 | else 38 | return 0.f; 39 | } 40 | 41 | //------------------------------------------------------------------------- 42 | 43 | @Override 44 | public String toString() 45 | { 46 | return "PassMove"; 47 | } 48 | 49 | //------------------------------------------------------------------------- 50 | 51 | @Override 52 | public String generateTikzCode(final Game game) 53 | { 54 | return "\\node[rectangle,draw{,REL_POS}] ({LABEL}) {Pass};"; 55 | } 56 | 57 | //------------------------------------------------------------------------- 58 | 59 | /** 60 | * @return The singleton instance 61 | */ 62 | public static PassMoveFeature instance() 63 | { 64 | return INSTANCE; 65 | } 66 | 67 | //------------------------------------------------------------------------- 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Features/src/features/aspatial/SwapMoveFeature.java: -------------------------------------------------------------------------------- 1 | package features.aspatial; 2 | 3 | import game.Game; 4 | import other.move.Move; 5 | import other.state.State; 6 | 7 | /** 8 | * Binary feature that has a value of 1.0 for any move that is a swap move. 9 | * 10 | * @author Dennis Soemers 11 | */ 12 | public class SwapMoveFeature extends AspatialFeature 13 | { 14 | 15 | //------------------------------------------------------------------------- 16 | 17 | /** The singleton instance */ 18 | private static final SwapMoveFeature INSTANCE = new SwapMoveFeature(); 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** 23 | * Private: singleton 24 | */ 25 | private SwapMoveFeature() 26 | { 27 | // Do nothing 28 | } 29 | 30 | //------------------------------------------------------------------------- 31 | 32 | @Override 33 | public float featureVal(final State state, final Move move) 34 | { 35 | if (move.isSwap()) 36 | return 1.f; 37 | else 38 | return 0.f; 39 | } 40 | 41 | //------------------------------------------------------------------------- 42 | 43 | @Override 44 | public String toString() 45 | { 46 | return "SwapMove"; 47 | } 48 | 49 | //------------------------------------------------------------------------- 50 | 51 | @Override 52 | public String generateTikzCode(final Game game) 53 | { 54 | return "\\node[rectangle,draw{,REL_POS}] ({LABEL}) {Swap};"; 55 | } 56 | 57 | //------------------------------------------------------------------------- 58 | 59 | /** 60 | * @return The singleton instance 61 | */ 62 | public static SwapMoveFeature instance() 63 | { 64 | return INSTANCE; 65 | } 66 | 67 | //------------------------------------------------------------------------- 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Features/src/features/spatial/graph_search/Path.java: -------------------------------------------------------------------------------- 1 | package features.spatial.graph_search; 2 | 3 | import java.util.List; 4 | 5 | import features.spatial.Walk; 6 | import other.topology.TopologyElement; 7 | 8 | /** 9 | * A Path used in graph search algorithms for features. A path consists of a 10 | * starting site, a destination site (may be null for off-board, or may be 11 | * the same as the start vertex for a 0-length path), and a Walk that moves from 12 | * the start to the destination. 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public class Path 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** List of sites on this path */ 22 | protected final List sites; 23 | 24 | /** */ 25 | protected final Walk walk; 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | /** 30 | * Constructor 31 | * @param sites 32 | * @param walk 33 | */ 34 | public Path(final List sites, final Walk walk) 35 | { 36 | this.sites = sites; 37 | this.walk = walk; 38 | } 39 | 40 | //------------------------------------------------------------------------- 41 | 42 | /** 43 | * @return Destination Vertex 44 | */ 45 | public TopologyElement destination() 46 | { 47 | return sites.get(sites.size() - 1); 48 | } 49 | 50 | /** 51 | * @return Start vertex 52 | */ 53 | public TopologyElement start() 54 | { 55 | return sites.get(0); 56 | } 57 | 58 | /** 59 | * @return All sites on the path 60 | */ 61 | public List sites() 62 | { 63 | return sites; 64 | } 65 | 66 | /** 67 | * @return The Walk 68 | */ 69 | public Walk walk() 70 | { 71 | return walk; 72 | } 73 | 74 | //------------------------------------------------------------------------- 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Features/src/features/spatial/instances/BitwiseTest.java: -------------------------------------------------------------------------------- 1 | package features.spatial.instances; 2 | 3 | import game.types.board.SiteType; 4 | import other.state.State; 5 | 6 | /** 7 | * Interface for classes that can perform bitwise tests on game states. 8 | * 9 | * The primary class implementing this interface is the general Feature 10 | * Instance, but there are also some more specific subclasses that only 11 | * need a small part of the full Feature Instance functionality, and can 12 | * run their tests more efficiently. 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public interface BitwiseTest 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** 22 | * @param state 23 | * @return True if this test matches the given game state 24 | */ 25 | public boolean matches(final State state); 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | /** 30 | * @return True if and only if this test automatically returns true 31 | */ 32 | public boolean hasNoTests(); 33 | 34 | /** 35 | * @return True if and only if this test only requires a single chunk to 36 | * be empty. 37 | */ 38 | public boolean onlyRequiresSingleMustEmpty(); 39 | 40 | /** 41 | * @return True if and only if this test only requires a single chunk to be 42 | * owned by a specific player. 43 | */ 44 | public boolean onlyRequiresSingleMustWho(); 45 | 46 | /** 47 | * @return True if and only if this test only requires a single chunk to 48 | * contain a specific component. 49 | */ 50 | public boolean onlyRequiresSingleMustWhat(); 51 | 52 | /** 53 | * @return GraphElementType that this test applies to 54 | */ 55 | public SiteType graphElementType(); 56 | 57 | //------------------------------------------------------------------------- 58 | 59 | } 60 | -------------------------------------------------------------------------------- /AI/src/search/mcts/selection/SelectionStrategy.java: -------------------------------------------------------------------------------- 1 | package search.mcts.selection; 2 | 3 | import org.json.JSONObject; 4 | 5 | import search.mcts.MCTS; 6 | import search.mcts.nodes.BaseNode; 7 | 8 | /** 9 | * Interface for Selection strategies for MCTS 10 | * 11 | * @author Dennis Soemers 12 | * 13 | */ 14 | public interface SelectionStrategy 15 | { 16 | 17 | //------------------------------------------------------------------------- 18 | 19 | /** 20 | * Should be implemented to select the index of a child of the current 21 | * node to traverse to. 22 | * 23 | * @param mcts 24 | * @param current 25 | * @return Index of child. 26 | */ 27 | public int select(final MCTS mcts, final BaseNode current); 28 | 29 | //------------------------------------------------------------------------- 30 | 31 | /** 32 | * @return Flags indicating stats that should be backpropagated 33 | */ 34 | public int backpropFlags(); 35 | 36 | /** 37 | * @return Flags indicating special things we want to do when expanding nodes 38 | */ 39 | public int expansionFlags(); 40 | 41 | /** 42 | * Customize the selection strategy based on a list of given string inputs 43 | * 44 | * @param inputs 45 | */ 46 | public void customise(final String[] inputs); 47 | 48 | //------------------------------------------------------------------------- 49 | 50 | /** 51 | * @param json 52 | * @return Selection strategy constructed from given JSON object 53 | */ 54 | public static SelectionStrategy fromJson(final JSONObject json) 55 | { 56 | SelectionStrategy selection = null; 57 | final String strategy = json.getString("strategy"); 58 | 59 | if (strategy.equalsIgnoreCase("UCB1")) 60 | { 61 | return new UCB1(); 62 | } 63 | 64 | return selection; 65 | } 66 | 67 | //------------------------------------------------------------------------- 68 | 69 | } 70 | -------------------------------------------------------------------------------- /AI/src/decision_trees/classifiers/DecisionLeafNode.java: -------------------------------------------------------------------------------- 1 | package decision_trees.classifiers; 2 | 3 | import features.FeatureVector; 4 | 5 | /** 6 | * Leaf node in a feature-based decision tree, with probabilities for classes. 7 | * 8 | * @author Dennis Soemers 9 | */ 10 | public class DecisionLeafNode extends DecisionTreeNode 11 | { 12 | 13 | //------------------------------------------------------------------------- 14 | 15 | /** Predicted probability of being a bottom-25% move */ 16 | protected final float bottom25Prob; 17 | 18 | /** Predicted probability of being a move in the Interquartile Range */ 19 | protected final float iqrProb; 20 | 21 | /** Predicted probability of being a top-25% move */ 22 | protected final float top25Prob; 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** 27 | * Constructor 28 | * @param bottom25Prob 29 | * @param iqrProb 30 | * @param top25Prob 31 | */ 32 | public DecisionLeafNode 33 | ( 34 | final float bottom25Prob, 35 | final float iqrProb, 36 | final float top25Prob 37 | ) 38 | { 39 | this.bottom25Prob = bottom25Prob; 40 | this.iqrProb = iqrProb; 41 | this.top25Prob = top25Prob; 42 | } 43 | 44 | //------------------------------------------------------------------------- 45 | 46 | @Override 47 | public float predict(final FeatureVector featureVector) 48 | { 49 | return top25Prob * (1.f - bottom25Prob); 50 | } 51 | 52 | //------------------------------------------------------------------------- 53 | 54 | @Override 55 | public metadata.ai.features.trees.classifiers.DecisionTreeNode toMetadataNode() 56 | { 57 | return new metadata.ai.features.trees.classifiers.Leaf(Float.valueOf(bottom25Prob), Float.valueOf(iqrProb), Float.valueOf(top25Prob)); 58 | } 59 | 60 | //------------------------------------------------------------------------- 61 | 62 | } 63 | -------------------------------------------------------------------------------- /AI/src/utils/RandomAI.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import game.Game; 6 | import main.collections.FastArrayList; 7 | import other.AI; 8 | import other.context.Context; 9 | import other.move.Move; 10 | 11 | /** 12 | * AI player which selects actions uniformly at random. 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public class RandomAI extends AI 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** Our player index */ 22 | protected int player = -1; 23 | 24 | /** The last move we returned */ 25 | protected Move lastReturnedMove = null; 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | /** 30 | * Constructor 31 | */ 32 | public RandomAI() 33 | { 34 | friendlyName = "Random"; 35 | } 36 | 37 | //------------------------------------------------------------------------- 38 | 39 | @Override 40 | public Move selectAction 41 | ( 42 | final Game game, 43 | final Context context, 44 | final double maxSeconds, 45 | final int maxIterations, 46 | final int maxDepth 47 | ) 48 | { 49 | FastArrayList legalMoves = game.moves(context).moves(); 50 | 51 | if (!game.isAlternatingMoveGame()) 52 | legalMoves = AIUtils.extractMovesForMover(legalMoves, player); 53 | 54 | final int r = ThreadLocalRandom.current().nextInt(legalMoves.size()); 55 | final Move move = legalMoves.get(r); 56 | lastReturnedMove = move; 57 | return move; 58 | } 59 | 60 | /** 61 | * @return The last move we returned 62 | */ 63 | public Move lastReturnedMove() 64 | { 65 | return lastReturnedMove; 66 | } 67 | 68 | @Override 69 | public void initAI(final Game game, final int playerID) 70 | { 71 | this.player = playerID; 72 | lastReturnedMove = null; 73 | } 74 | 75 | //------------------------------------------------------------------------- 76 | 77 | } 78 | -------------------------------------------------------------------------------- /AI/src/search/mcts/finalmoveselection/ProportionalExpVisitCount.java: -------------------------------------------------------------------------------- 1 | package search.mcts.finalmoveselection; 2 | 3 | import main.collections.FVector; 4 | import other.move.Move; 5 | import search.mcts.MCTS; 6 | import search.mcts.nodes.BaseNode; 7 | 8 | /** 9 | * Selects moves proportionally to exponentiated visit counts, 10 | * 11 | * This strategy should never be used for "competitive" play, but can be useful 12 | * to generate more variety in experience in self-play. 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public final class ProportionalExpVisitCount implements FinalMoveSelectionStrategy 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** Temperature parameter tau (all visit counts will be raised to this power to generate distribution) */ 22 | protected double tau; 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** 27 | * Constructor with temperature parameter tau 28 | * (1.0 = proportional to visit counts, 0.0 = greedy) 29 | * @param tau 30 | */ 31 | public ProportionalExpVisitCount(final double tau) 32 | { 33 | this.tau = tau; 34 | } 35 | 36 | //------------------------------------------------------------------------- 37 | 38 | @Override 39 | public Move selectMove(final MCTS mcts, final BaseNode rootNode) 40 | { 41 | final FVector distribution = rootNode.computeVisitCountPolicy(tau); 42 | final int actionIndex = distribution.sampleProportionally(); 43 | return rootNode.nthLegalMove(actionIndex); 44 | } 45 | 46 | //------------------------------------------------------------------------- 47 | 48 | @Override 49 | public void customise(final String[] inputs) 50 | { 51 | for (final String input : inputs) 52 | { 53 | if (input.startsWith("tau=")) 54 | { 55 | tau = Double.parseDouble(input.substring("tau=".length())); 56 | } 57 | } 58 | } 59 | 60 | //------------------------------------------------------------------------- 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Features/src/features/Feature.java: -------------------------------------------------------------------------------- 1 | package features; 2 | 3 | import features.aspatial.AspatialFeature; 4 | import features.aspatial.InterceptFeature; 5 | import features.aspatial.PassMoveFeature; 6 | import features.aspatial.SwapMoveFeature; 7 | import features.spatial.AbsoluteFeature; 8 | import features.spatial.RelativeFeature; 9 | import game.Game; 10 | 11 | /** 12 | * Abstract class for features; can be spatial or aspatial. 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public abstract class Feature 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** 22 | * @param string 23 | * @return Feature constructed from given stirng 24 | */ 25 | public static Feature fromString(final String string) 26 | { 27 | if (string.contains("abs:")) 28 | return new AbsoluteFeature(string); 29 | else if (string.contains("rel:")) 30 | return new RelativeFeature(string); 31 | else 32 | return aspatialFromString(string); 33 | } 34 | 35 | //------------------------------------------------------------------------- 36 | 37 | /** 38 | * @param string 39 | * @return Aspatial feature constructed from given string 40 | */ 41 | private static AspatialFeature aspatialFromString(final String string) 42 | { 43 | if (string.equals("PassMove")) 44 | return PassMoveFeature.instance(); 45 | else if (string.equals("SwapMove")) 46 | return SwapMoveFeature.instance(); 47 | else if (string.equals("Intercept")) 48 | return InterceptFeature.instance(); 49 | else 50 | System.err.println("Cannot construct aspatial feature from string: " + string); 51 | 52 | return null; 53 | } 54 | 55 | //------------------------------------------------------------------------- 56 | 57 | /** 58 | * @param game Game to visualise for 59 | * @return Tikz code to visualise this feature in a Tikz environment in LaTeX. 60 | */ 61 | public abstract String generateTikzCode(final Game game); 62 | 63 | //------------------------------------------------------------------------- 64 | 65 | } 66 | -------------------------------------------------------------------------------- /AI/src/training/ExperienceSample.java: -------------------------------------------------------------------------------- 1 | package training; 2 | 3 | import java.util.BitSet; 4 | 5 | import features.FeatureVector; 6 | import features.feature_sets.BaseFeatureSet; 7 | import main.collections.FVector; 8 | import main.collections.FastArrayList; 9 | import other.move.Move; 10 | import other.state.State; 11 | 12 | /** 13 | * Abstract class for a sample of experience 14 | * 15 | * @author Dennis Soemers 16 | */ 17 | public abstract class ExperienceSample 18 | { 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** 23 | * Should be implemented to (generate and) return feature vectors corresponding 24 | * to the moves that were legal in this sample of experience. Can use the given 25 | * feature set to generate them, but can also return already-cached ones. 26 | * 27 | * @param featureSet 28 | * @return Feature vectors corresponding to this sample of experience 29 | */ 30 | public abstract FeatureVector[] generateFeatureVectors(final BaseFeatureSet featureSet); 31 | 32 | /** 33 | * Should be implemented to return an expert distribution over actions. 34 | * 35 | * @return Expert distribution over actions 36 | */ 37 | public abstract FVector expertDistribution(); 38 | 39 | /** 40 | * @return Game state 41 | */ 42 | public abstract State gameState(); 43 | 44 | /** 45 | * @return From-position, for features, from last decision move. 46 | */ 47 | public abstract int lastFromPos(); 48 | 49 | /** 50 | * @return To-position, for features, from last decision move. 51 | */ 52 | public abstract int lastToPos(); 53 | 54 | /** 55 | * @return List of legal moves 56 | */ 57 | public abstract FastArrayList moves(); 58 | 59 | /** 60 | * @return BitSet of winning moves 61 | */ 62 | public abstract BitSet winningMoves(); 63 | 64 | /** 65 | * @return BitSet of losing moves 66 | */ 67 | public abstract BitSet losingMoves(); 68 | 69 | /** 70 | * @return BitSet of anti-defeating moves 71 | */ 72 | public abstract BitSet antiDefeatingMoves(); 73 | 74 | //------------------------------------------------------------------------- 75 | 76 | } 77 | -------------------------------------------------------------------------------- /AI/src/search/mcts/finalmoveselection/MaxAvgScore.java: -------------------------------------------------------------------------------- 1 | package search.mcts.finalmoveselection; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import other.move.Move; 6 | import other.state.State; 7 | import search.mcts.MCTS; 8 | import search.mcts.nodes.BaseNode; 9 | 10 | /** 11 | * Selects move corresponding to the child with the highest average score 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public final class MaxAvgScore implements FinalMoveSelectionStrategy 16 | { 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | @Override 21 | public Move selectMove(final MCTS mcts, final BaseNode rootNode) 22 | { 23 | int bestIdx = -1; 24 | double maxAvgScore = Double.NEGATIVE_INFINITY; 25 | int numBestFound = 0; 26 | 27 | final State state = rootNode.contextRef().state(); 28 | final int numChildren = rootNode.numLegalMoves(); 29 | final int moverAgent = state.playerToAgent(state.mover()); 30 | 31 | for (int i = 0; i < numChildren; ++i) 32 | { 33 | final BaseNode child = rootNode.childForNthLegalMove(i); 34 | final double avgScore; 35 | 36 | if (child == null) 37 | avgScore = rootNode.valueEstimateUnvisitedChildren(moverAgent); 38 | else 39 | avgScore = child.expectedScore(moverAgent); 40 | 41 | if (avgScore > maxAvgScore) 42 | { 43 | maxAvgScore = avgScore; 44 | bestIdx = i; 45 | numBestFound = 1; 46 | } 47 | else if (avgScore == maxAvgScore && 48 | ThreadLocalRandom.current().nextInt() % ++numBestFound == 0) 49 | { 50 | bestIdx = i; 51 | } 52 | } 53 | 54 | return rootNode.nthLegalMove(bestIdx); 55 | } 56 | 57 | //------------------------------------------------------------------------- 58 | 59 | @Override 60 | public void customise(final String[] inputs) 61 | { 62 | // do nothing 63 | } 64 | 65 | //------------------------------------------------------------------------- 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Features/src/features/feature_sets/network/PropFeatureInstanceSet.java: -------------------------------------------------------------------------------- 1 | package features.feature_sets.network; 2 | 3 | import java.util.ArrayList; 4 | import java.util.BitSet; 5 | import java.util.List; 6 | 7 | import features.spatial.instances.FeatureInstance; 8 | import other.state.State; 9 | 10 | /** 11 | * A set of propositions and feature instances. 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public class PropFeatureInstanceSet 16 | { 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | /** Array of feature instances */ 21 | protected final FeatureInstance[] featureInstances; 22 | 23 | /** Array of PropNodes */ 24 | protected final PropNode[] propNodes; 25 | 26 | //------------------------------------------------------------------------- 27 | 28 | /** 29 | * Constructor 30 | * @param featureInstances 31 | * @param propNodes 32 | */ 33 | public PropFeatureInstanceSet(final FeatureInstance[] featureInstances, final PropNode[] propNodes) 34 | { 35 | this.featureInstances = featureInstances; 36 | this.propNodes = propNodes; 37 | } 38 | 39 | //------------------------------------------------------------------------- 40 | 41 | /** 42 | * @param state 43 | * @return List of active instances for given state 44 | */ 45 | public List getActiveInstances(final State state) 46 | { 47 | final List active = new ArrayList(); 48 | 49 | final BitSet activeNodes = new BitSet(propNodes.length); 50 | activeNodes.set(0, propNodes.length); 51 | 52 | final BitSet activeInstances = new BitSet(featureInstances.length); 53 | activeInstances.set(0, featureInstances.length); 54 | 55 | for (int i = activeNodes.nextSetBit(0); i >= 0; i = activeNodes.nextSetBit(i + 1)) 56 | { 57 | propNodes[i].eval(state, activeNodes, activeInstances); 58 | } 59 | 60 | for (int i = activeInstances.nextSetBit(0); i >= 0; i = activeInstances.nextSetBit(i + 1)) 61 | { 62 | active.add(featureInstances[i]); 63 | } 64 | 65 | return active; 66 | } 67 | 68 | //------------------------------------------------------------------------- 69 | 70 | } 71 | -------------------------------------------------------------------------------- /AI/src/search/mcts/backpropagation/AlphaGoBackprop.java: -------------------------------------------------------------------------------- 1 | package search.mcts.backpropagation; 2 | 3 | import other.context.Context; 4 | import search.mcts.MCTS; 5 | import search.mcts.nodes.BaseNode; 6 | import utils.AIUtils; 7 | 8 | /** 9 | * An AlphaGo-style backpropagation, that returns a convex combination 10 | * of a heuristic value function evaluated at the expanded node and 11 | * a heuristic value function evaluated at the end of a playout. 12 | * 13 | * Can also be used for Alpha(Go) Zero style backpropagations 14 | * by simply using a weight of 0.0 for playout value, and 1.0 15 | * for the expanded node's value (plus, for efficiency, using 16 | * 0-length playouts). 17 | * 18 | * @author Dennis Soemers 19 | */ 20 | public class AlphaGoBackprop extends BackpropagationStrategy 21 | { 22 | 23 | @Override 24 | public void computeUtilities 25 | ( 26 | final MCTS mcts, 27 | final BaseNode startNode, 28 | final Context context, 29 | final double[] utilities, 30 | final int numPlayoutMoves 31 | ) 32 | { 33 | assert (mcts.heuristics() != null); 34 | 35 | final double playoutValueWeight = mcts.playoutValueWeight(); 36 | 37 | final double[] nodeHeuristicValues; 38 | 39 | if (playoutValueWeight < 1.0) 40 | { 41 | // Mix value function of expanded node with playout outcome (like AlphaGo) 42 | nodeHeuristicValues = startNode.heuristicValueEstimates(); 43 | } 44 | else 45 | { 46 | // This array is irrelevant 47 | nodeHeuristicValues = new double[utilities.length]; 48 | } 49 | 50 | if (context.active() && playoutValueWeight > 0.0) 51 | { 52 | // Playout did not terminate, so should also run heuristics at end of playout 53 | final double[] playoutHeuristicValues = AIUtils.heuristicValueEstimates(context, mcts.heuristics()); 54 | for (int p = 1; p < utilities.length; ++p) 55 | { 56 | utilities[p] = playoutHeuristicValues[p]; 57 | } 58 | } 59 | 60 | for (int p = 1; p < utilities.length; ++p) 61 | { 62 | // Mix node and playout values 63 | utilities[p] = playoutValueWeight * utilities[p] + (1.0 - playoutValueWeight) * nodeHeuristicValues[p]; 64 | } 65 | } 66 | 67 | @Override 68 | public int backpropagationFlags() 69 | { 70 | return 0; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /AI/src/search/mcts/backpropagation/QualitativeBonus.java: -------------------------------------------------------------------------------- 1 | package search.mcts.backpropagation; 2 | 3 | import main.math.statistics.IncrementalStats; 4 | import other.context.Context; 5 | import search.mcts.MCTS; 6 | import search.mcts.nodes.BaseNode; 7 | import utils.AIUtils; 8 | 9 | /** 10 | * Implements a Qualitative bonus (based on heuristic value function estimates), 11 | * as described in "Quality-based Rewards for Monte-Carlo Tree Search Simulations" 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public class QualitativeBonus extends BackpropagationStrategy 16 | { 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | /** Constant used in sigmoid squashing of bonus */ 21 | private final double k = 1.4; 22 | 23 | /** Weight assigned to bonuses */ 24 | private final double a = 0.25; 25 | 26 | //------------------------------------------------------------------------- 27 | 28 | @Override 29 | public void computeUtilities 30 | ( 31 | final MCTS mcts, 32 | final BaseNode startNode, 33 | final Context context, 34 | final double[] utilities, 35 | final int numPlayoutMoves 36 | ) 37 | { 38 | assert (mcts.heuristics() != null); 39 | 40 | final double[] heuristicValues = AIUtils.heuristicValueBonusEstimates(context, mcts.heuristics()); 41 | final IncrementalStats[] heuristicStats = mcts.heuristicStats(); 42 | 43 | for (int p = 1; p < heuristicValues.length; ++p) 44 | { 45 | final IncrementalStats stats = heuristicStats[p]; 46 | final double q = heuristicValues[p]; 47 | final double std = stats.getStd(); 48 | 49 | if (std > 0.0) 50 | { 51 | // Apply bonus 52 | final double lambda = (q - stats.getMean()) / std; 53 | final double bonus = -1.0 + (2.0 / (1.0 + Math.exp(-k * lambda))); 54 | utilities[p] += a * bonus; // Not including sign(r) since our bonuses are from p perspective, not from winner's perspective 55 | } 56 | 57 | // Update incremental stats tracker 58 | stats.observe(q); 59 | } 60 | } 61 | 62 | @Override 63 | public int backpropagationFlags() 64 | { 65 | return BackpropagationStrategy.GLOBAL_HEURISTIC_STATS; 66 | } 67 | 68 | //------------------------------------------------------------------------- 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Features/src/features/feature_sets/network/PropNode.java: -------------------------------------------------------------------------------- 1 | package features.feature_sets.network; 2 | 3 | import java.util.BitSet; 4 | 5 | import features.spatial.instances.AtomicProposition; 6 | import other.state.State; 7 | 8 | /** 9 | * A prop node in the PropFeatureInstanceSet representation. 10 | * 11 | * @author Dennis Soemers 12 | */ 13 | public class PropNode 14 | { 15 | 16 | //------------------------------------------------------------------------- 17 | 18 | /** Unique index of this node in array */ 19 | protected final int index; 20 | 21 | /** Atomic proposition which must be true for this node to be true */ 22 | protected final AtomicProposition proposition; 23 | 24 | /** Bitset of instances to deactivate if this node is false */ 25 | protected final BitSet dependentInstances = new BitSet(); 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | /** 30 | * Constructor 31 | * @param index 32 | * @param proposition 33 | */ 34 | public PropNode(final int index, final AtomicProposition proposition) 35 | { 36 | this.index = index; 37 | this.proposition = proposition; 38 | } 39 | 40 | //------------------------------------------------------------------------- 41 | 42 | /** 43 | * Evaluate the given state. 44 | * @param state 45 | * @param activeNodes Bitset of nodes that are still active. 46 | * @param activeInstances Bitset of feature instances that are active. 47 | */ 48 | public void eval(final State state, final BitSet activeNodes, final BitSet activeInstances) 49 | { 50 | if (activeInstances.intersects(dependentInstances)) // if false, might as well not check anything 51 | { 52 | if (!proposition.matches(state)) // Requirement not satisfied 53 | activeInstances.andNot(dependentInstances); 54 | } 55 | } 56 | 57 | //------------------------------------------------------------------------- 58 | 59 | /** 60 | * Mark an instance ID that we should set to false if our proposition is false 61 | * @param instanceID 62 | */ 63 | public void setDependentInstance(final int instanceID) 64 | { 65 | dependentInstances.set(instanceID); 66 | } 67 | 68 | /** 69 | * @return Our proposition 70 | */ 71 | public AtomicProposition proposition() 72 | { 73 | return proposition; 74 | } 75 | 76 | //------------------------------------------------------------------------- 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Features/src/features/feature_sets/network/FeaturePropNode.java: -------------------------------------------------------------------------------- 1 | package features.feature_sets.network; 2 | 3 | import java.util.BitSet; 4 | 5 | import features.spatial.instances.AtomicProposition; 6 | import other.state.State; 7 | 8 | /** 9 | * A feature prop node in the FeaturePropSet representation 10 | * 11 | * @author Dennis Soemers 12 | */ 13 | public class FeaturePropNode 14 | { 15 | 16 | //------------------------------------------------------------------------- 17 | 18 | /** Unique index of this node in array */ 19 | protected final int index; 20 | 21 | /** Atomic proposition which must be true for this node to be true */ 22 | protected final AtomicProposition proposition; 23 | 24 | /** Bitset of feature indices to deactivate if this node is false */ 25 | protected final BitSet dependentFeatures = new BitSet(); 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | /** 30 | * Constructor 31 | * @param index 32 | * @param proposition 33 | */ 34 | public FeaturePropNode(final int index, final AtomicProposition proposition) 35 | { 36 | this.index = index; 37 | this.proposition = proposition; 38 | } 39 | 40 | //------------------------------------------------------------------------- 41 | 42 | /** 43 | * Evaluate the given state. 44 | * @param state 45 | * @param activeNodes Bitset of nodes that are still active. 46 | * @param activeFeatures Bitset of feature indices that are active. 47 | */ 48 | public void eval(final State state, final BitSet activeNodes, final BitSet activeFeatures) 49 | { 50 | if (activeFeatures.intersects(dependentFeatures)) // if false, might as well not check anything 51 | { 52 | if (!proposition.matches(state)) // Requirement not satisfied 53 | activeFeatures.andNot(dependentFeatures); 54 | } 55 | } 56 | 57 | //------------------------------------------------------------------------- 58 | 59 | /** 60 | * Mark a feature ID that we should set to false if our proposition is false 61 | * @param featureID 62 | */ 63 | public void setDependentFeature(final int featureID) 64 | { 65 | dependentFeatures.set(featureID); 66 | } 67 | 68 | /** 69 | * @return Our proposition 70 | */ 71 | public AtomicProposition proposition() 72 | { 73 | return proposition; 74 | } 75 | 76 | //------------------------------------------------------------------------- 77 | 78 | } 79 | -------------------------------------------------------------------------------- /AI/src/optimisers/Optimiser.java: -------------------------------------------------------------------------------- 1 | package optimisers; 2 | 3 | import java.io.Serializable; 4 | 5 | import main.collections.FVector; 6 | 7 | /** 8 | * Base class for optimizers. All optimizers are pretty much assumed to be 9 | * variants of Mini-Batch Gradient Descent. 10 | * 11 | * @author Dennis Soemers 12 | */ 13 | public abstract class Optimiser implements Serializable 14 | { 15 | 16 | //------------------------------------------------------------------------- 17 | 18 | /** */ 19 | private static final long serialVersionUID = 1L; 20 | 21 | //------------------------------------------------------------------------- 22 | 23 | /** Base step-size (or learning rate) to use */ 24 | protected final float baseStepSize; 25 | 26 | //------------------------------------------------------------------------- 27 | 28 | /** 29 | * Constructor 30 | * @param baseStepSize 31 | */ 32 | public Optimiser(final float baseStepSize) 33 | { 34 | this.baseStepSize = baseStepSize; 35 | } 36 | 37 | //------------------------------------------------------------------------- 38 | 39 | /** 40 | * Should be implemented to adjust the given vector of parameters in an 41 | * attempt to maximise an objective function. The objective function is 42 | * implied by a vector of (estimates of) gradients of that objective 43 | * function with respect to the trainable parameters. 44 | * 45 | * @param params 46 | * Parameters to train 47 | * @param gradients 48 | * Vector of (estimates of) gradients of objective with respect to params. 49 | */ 50 | public abstract void maximiseObjective(final FVector params, final FVector gradients); 51 | 52 | /** 53 | * Calls maximiseObjective() with negated gradients, in order to minimize 54 | * the objective. 55 | * 56 | * @param params 57 | * @param gradients 58 | */ 59 | public final void minimiseObjective(final FVector params, final FVector gradients) 60 | { 61 | final FVector negatedGrads = gradients.copy(); 62 | negatedGrads.mult(-1.f); 63 | maximiseObjective(params, negatedGrads); 64 | } 65 | 66 | //------------------------------------------------------------------------- 67 | 68 | /** 69 | * Writes this optimiser's internal state to a binary file 70 | * @param filepath 71 | */ 72 | public abstract void writeToFile(final String filepath); 73 | 74 | //------------------------------------------------------------------------- 75 | 76 | } 77 | -------------------------------------------------------------------------------- /AI/src/search/mcts/playout/RandomPlayout.java: -------------------------------------------------------------------------------- 1 | package search.mcts.playout; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import game.Game; 6 | import other.context.Context; 7 | import other.trial.Trial; 8 | import search.mcts.MCTS; 9 | 10 | /** 11 | * A completely random Play-out strategy (selects actions according 12 | * to a uniform distribution). 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public final class RandomPlayout implements PlayoutStrategy 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** Auto-end playouts in a draw if they take more turns than this */ 22 | protected int playoutTurnLimit = -1; 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** 27 | * Constructor 28 | */ 29 | public RandomPlayout() 30 | { 31 | playoutTurnLimit = -1; // no limit 32 | } 33 | 34 | /** 35 | * Constructor 36 | * @param playoutTurnLimit 37 | */ 38 | public RandomPlayout(final int playoutTurnLimit) 39 | { 40 | this.playoutTurnLimit = playoutTurnLimit; 41 | } 42 | 43 | //------------------------------------------------------------------------- 44 | 45 | @Override 46 | public Trial runPlayout(final MCTS mcts, final Context context) 47 | { 48 | return context.game().playout(context, null, 1.0, null, 0, playoutTurnLimit, ThreadLocalRandom.current()); 49 | } 50 | 51 | @Override 52 | public int backpropFlags() 53 | { 54 | return 0; 55 | } 56 | 57 | //------------------------------------------------------------------------- 58 | 59 | @Override 60 | public boolean playoutSupportsGame(final Game game) 61 | { 62 | if (game.isDeductionPuzzle()) 63 | return (playoutTurnLimit() > 0); 64 | else 65 | return true; 66 | } 67 | 68 | @Override 69 | public void customise(final String[] inputs) 70 | { 71 | for (int i = 1; i < inputs.length; ++i) 72 | { 73 | final String input = inputs[i]; 74 | 75 | if (input.toLowerCase().startsWith("playoutturnlimit=")) 76 | { 77 | playoutTurnLimit = 78 | Integer.parseInt 79 | ( 80 | input.substring("playoutturnlimit=".length()) 81 | ); 82 | } 83 | } 84 | } 85 | 86 | /** 87 | * @return The turn limit we use in playouts 88 | */ 89 | public int playoutTurnLimit() 90 | { 91 | return playoutTurnLimit; 92 | } 93 | 94 | //------------------------------------------------------------------------- 95 | 96 | } 97 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/menageries/NaiveSelfPlay.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.menageries; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import game.Game; 7 | import metadata.ai.features.Features; 8 | import metadata.ai.heuristics.Heuristics; 9 | import other.context.Context; 10 | import training.expert_iteration.ExpertPolicy; 11 | import training.expert_iteration.params.AgentsParams; 12 | 13 | /** 14 | * Naive self-play menagerie: always uses the latest version of the trained agent, 15 | * for all player IDs. 16 | * 17 | * @author Dennis Soemers 18 | */ 19 | public class NaiveSelfPlay implements Menagerie 20 | { 21 | 22 | //------------------------------------------------------------------------- 23 | 24 | /** Our dev checkpoint (the only one we actually use) */ 25 | private AgentCheckpoint dev; 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | @Override 30 | public DrawnAgentsData drawAgents(final Game game, final AgentsParams agentsParams) 31 | { 32 | final List agents = new ArrayList(game.players().count() + 1); 33 | agents.add(null); 34 | for (int p = 1; p <= game.players().count(); ++p) 35 | { 36 | agents.add(dev.generateAgent(game, agentsParams)); 37 | } 38 | return new DrawnAgentsData(agents); 39 | } 40 | 41 | //------------------------------------------------------------------------- 42 | 43 | @Override 44 | public void initialisePopulation 45 | ( 46 | final Game game, 47 | final AgentsParams agentsParams, 48 | final Features features, 49 | final Heuristics heuristics 50 | ) 51 | { 52 | dev = new AgentCheckpoint(agentsParams.expertAI, "Dev", features, heuristics); 53 | } 54 | 55 | @Override 56 | public void updateDevFeatures(final Features features) 57 | { 58 | dev = new AgentCheckpoint(dev.agentName, "Dev", features, dev.heuristicsMetadata); 59 | } 60 | 61 | @Override 62 | public void updateDevHeuristics(final Heuristics heuristics) 63 | { 64 | dev = new AgentCheckpoint(dev.agentName, "Dev", dev.featuresMetadata, heuristics); 65 | } 66 | 67 | @Override 68 | public void updateOutcome(final Context context, final DrawnAgentsData drawnAgentsData) 69 | { 70 | // Nothing to do here 71 | } 72 | 73 | @Override 74 | public String generateLog() 75 | { 76 | return null; 77 | } 78 | 79 | //------------------------------------------------------------------------- 80 | 81 | } 82 | -------------------------------------------------------------------------------- /AI/src/utils/ExponentialMovingAverage.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import java.io.BufferedOutputStream; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.ObjectOutputStream; 7 | import java.io.Serializable; 8 | 9 | /** 10 | * Utility class to incrementally keep track of exponential 11 | * moving averages. 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public class ExponentialMovingAverage implements Serializable 16 | { 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | /** */ 21 | private static final long serialVersionUID = 1L; 22 | 23 | //------------------------------------------------------------------------- 24 | 25 | /** Weight assigned to most recent point of data. 0 = all data points weighed equally */ 26 | protected final double alpha; 27 | 28 | /** Our running mean */ 29 | protected double runningMean = 0.0; 30 | 31 | /** Our denominator in running mean */ 32 | protected double denominator = 0.0; 33 | 34 | //------------------------------------------------------------------------- 35 | 36 | /** 37 | * Constructor (default alpha of 0.05) 38 | */ 39 | public ExponentialMovingAverage() 40 | { 41 | this(0.05); 42 | } 43 | 44 | /** 45 | * Constructor 46 | * @param alpha 47 | */ 48 | public ExponentialMovingAverage(final double alpha) 49 | { 50 | this.alpha = alpha; 51 | } 52 | 53 | //------------------------------------------------------------------------- 54 | 55 | /** 56 | * @return Our (exponential) moving average 57 | */ 58 | public double movingAvg() 59 | { 60 | return runningMean; 61 | } 62 | 63 | /** 64 | * Observe a new data point 65 | * @param data 66 | */ 67 | public void observe(final double data) 68 | { 69 | denominator = (1 - alpha) * denominator + 1; 70 | runningMean += (1.0 / denominator) * (data - runningMean); 71 | } 72 | 73 | //------------------------------------------------------------------------- 74 | 75 | /** 76 | * Writes this tracker to a binary file 77 | * @param filepath 78 | */ 79 | public void writeToFile(final String filepath) 80 | { 81 | try 82 | ( 83 | final ObjectOutputStream out = 84 | new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filepath))) 85 | ) 86 | { 87 | out.writeObject(this); 88 | out.flush(); 89 | out.close(); 90 | } 91 | catch (final IOException e) 92 | { 93 | e.printStackTrace(); 94 | } 95 | } 96 | 97 | //------------------------------------------------------------------------- 98 | 99 | } 100 | -------------------------------------------------------------------------------- /Features/src/features/spatial/cache/footprints/BaseFootprint.java: -------------------------------------------------------------------------------- 1 | package features.spatial.cache.footprints; 2 | 3 | import main.collections.ChunkSet; 4 | 5 | /** 6 | * Wrapper class for masks that represent the key-specific (specific to 7 | * player index / from-pos / to-pos) footprint of a complete Feature Set. 8 | * 9 | * @author Dennis Soemers 10 | */ 11 | public abstract class BaseFootprint 12 | { 13 | 14 | //------------------------------------------------------------------------- 15 | 16 | /** 17 | * @return Footprint on "empty" ChunkSet for cells 18 | */ 19 | public abstract ChunkSet emptyCell(); 20 | 21 | /** 22 | * @return Footprint on "empty" ChunkSet for vertices 23 | */ 24 | public abstract ChunkSet emptyVertex(); 25 | 26 | /** 27 | * @return Footprint on "empty" ChunkSet for edges 28 | */ 29 | public abstract ChunkSet emptyEdge(); 30 | 31 | /** 32 | * @return Footprint on "who" ChunkSet for cells 33 | */ 34 | public abstract ChunkSet whoCell(); 35 | 36 | /** 37 | * @return Footprint on "who" ChunkSet for vertices 38 | */ 39 | public abstract ChunkSet whoVertex(); 40 | 41 | /** 42 | * @return Footprint on "who" ChunkSet for edges 43 | */ 44 | public abstract ChunkSet whoEdge(); 45 | 46 | /** 47 | * @return Footprint on "what" ChunkSet for cells 48 | */ 49 | public abstract ChunkSet whatCell(); 50 | 51 | /** 52 | * @return Footprint on "what" ChunkSet for vertices 53 | */ 54 | public abstract ChunkSet whatVertex(); 55 | 56 | /** 57 | * @return Footprint on "what" ChunkSet for edges 58 | */ 59 | public abstract ChunkSet whatEdge(); 60 | 61 | //------------------------------------------------------------------------- 62 | 63 | /** 64 | * Adds the given other footprint to this one 65 | * @param other 66 | */ 67 | public void union(final BaseFootprint other) 68 | { 69 | if (other.emptyCell() != null) 70 | emptyCell().or(other.emptyCell()); 71 | if (other.emptyVertex() != null) 72 | emptyVertex().or(other.emptyVertex()); 73 | if (other.emptyEdge() != null) 74 | emptyEdge().or(other.emptyEdge()); 75 | 76 | if (other.whoCell() != null) 77 | whoCell().or(other.whoCell()); 78 | if (other.whoVertex() != null) 79 | whoVertex().or(other.whoVertex()); 80 | if (other.whoEdge() != null) 81 | whoEdge().or(other.whoEdge()); 82 | 83 | if (other.whatCell() != null) 84 | whatCell().or(other.whatCell()); 85 | if (other.whatVertex() != null) 86 | whatVertex().or(other.whatVertex()); 87 | if (other.whatEdge() != null) 88 | whatEdge().or(other.whatEdge()); 89 | } 90 | 91 | //------------------------------------------------------------------------- 92 | 93 | } 94 | -------------------------------------------------------------------------------- /AI/src/decision_trees/logits/LogitModelNode.java: -------------------------------------------------------------------------------- 1 | package decision_trees.logits; 2 | 3 | import features.Feature; 4 | import features.FeatureVector; 5 | import features.aspatial.AspatialFeature; 6 | import metadata.ai.features.trees.logits.Leaf; 7 | import metadata.ai.features.trees.logits.LogitNode; 8 | import metadata.ai.misc.Pair; 9 | 10 | /** 11 | * Leaf node in a feature-based logit tree, with a linear model. 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public class LogitModelNode extends LogitTreeNode 16 | { 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | /** Array of remaining features */ 21 | protected final Feature[] features; 22 | 23 | /** Array of weights for the remaining features */ 24 | protected final float[] weights; 25 | 26 | /** Array of feature indices */ 27 | protected final int[] featureIndices; 28 | 29 | //------------------------------------------------------------------------- 30 | 31 | /** 32 | * Constructor 33 | * @param features 34 | * @param weights 35 | */ 36 | public LogitModelNode 37 | ( 38 | final Feature[] features, 39 | final float[] weights 40 | ) 41 | { 42 | this.features = features; 43 | this.weights = weights; 44 | featureIndices = null; 45 | } 46 | 47 | /** 48 | * Constructor 49 | * @param features 50 | * @param weights 51 | * @param featureIndices 52 | */ 53 | public LogitModelNode 54 | ( 55 | final Feature[] features, 56 | final float[] weights, 57 | final int[] featureIndices 58 | ) 59 | { 60 | this.features = features; 61 | this.weights = weights; 62 | this.featureIndices = featureIndices; 63 | } 64 | 65 | //------------------------------------------------------------------------- 66 | 67 | @Override 68 | public float predict(final FeatureVector featureVector) 69 | { 70 | float dotProduct = 0.f; 71 | 72 | for (int i = 0; i < features.length; ++i) 73 | { 74 | final Feature feature = features[i]; 75 | final int featureIdx = featureIndices[i]; 76 | 77 | if (feature instanceof AspatialFeature) 78 | { 79 | dotProduct += featureVector.aspatialFeatureValues().get(featureIdx) * weights[i]; 80 | } 81 | else 82 | { 83 | if (featureVector.activeSpatialFeatureIndices().contains(featureIdx)) 84 | dotProduct += weights[i]; 85 | } 86 | } 87 | 88 | return dotProduct; 89 | } 90 | 91 | //------------------------------------------------------------------------- 92 | 93 | @Override 94 | public LogitNode toMetadataNode() 95 | { 96 | final Pair[] pairs = new Pair[features.length]; 97 | for (int i= 0; i < pairs.length; ++i) 98 | { 99 | pairs[i] = new Pair(features[i].toString(), Float.valueOf(weights[i])); 100 | } 101 | return new Leaf(pairs); 102 | } 103 | 104 | //------------------------------------------------------------------------- 105 | 106 | } 107 | -------------------------------------------------------------------------------- /Features/src/features/WeightVector.java: -------------------------------------------------------------------------------- 1 | package features; 2 | 3 | import main.collections.FVector; 4 | 5 | /** 6 | * Wrapper to represent a vector of weights. Internally stores it as just a single 7 | * vector, where the first N weights are for aspatial features, and the remaining 8 | * weights are for spatial features. 9 | * 10 | * @author Dennis Soemers 11 | */ 12 | public class WeightVector 13 | { 14 | 15 | //------------------------------------------------------------------------- 16 | 17 | /** Our vector of weights */ 18 | private final FVector weights; 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** 23 | * Constructor 24 | * @param weights 25 | */ 26 | public WeightVector(final FVector weights) 27 | { 28 | this.weights = weights; 29 | } 30 | 31 | /** 32 | * Copy constructor 33 | * @param other 34 | */ 35 | public WeightVector(final WeightVector other) 36 | { 37 | this.weights = new FVector(other.weights); 38 | } 39 | 40 | //------------------------------------------------------------------------- 41 | 42 | /** 43 | * @param featureVector 44 | * @return Dot product of this weight vector with given feature vector 45 | */ 46 | public float dot(final FeatureVector featureVector) 47 | { 48 | final FVector aspatialFeatureValues = featureVector.aspatialFeatureValues(); 49 | 50 | // This dot product call will only use the first N weights, where N is the length 51 | // of the aspatial feature values vector 52 | final float aspatialFeaturesVal = aspatialFeatureValues.dot(weights); 53 | 54 | // For the spatial features, use this offset (to skip weights for aspatial features) 55 | final int offset = aspatialFeatureValues.dim(); 56 | 57 | return aspatialFeaturesVal + weights.dotSparse(featureVector.activeSpatialFeatureIndices(), offset); 58 | } 59 | 60 | //------------------------------------------------------------------------- 61 | 62 | /** 63 | * @return Vector containing all weights; first those for aspatial features, followed 64 | * by those for spatial features. 65 | */ 66 | public FVector allWeights() 67 | { 68 | return weights; 69 | } 70 | 71 | //------------------------------------------------------------------------- 72 | 73 | @Override 74 | public int hashCode() 75 | { 76 | final int prime = 31; 77 | int result = 1; 78 | result = prime * result + ((weights == null) ? 0 : weights.hashCode()); 79 | return result; 80 | } 81 | 82 | @Override 83 | public boolean equals(final Object obj) 84 | { 85 | if (this == obj) 86 | return true; 87 | 88 | if (!(obj instanceof WeightVector)) 89 | return false; 90 | 91 | final WeightVector other = (WeightVector) obj; 92 | if (weights == null) 93 | { 94 | if (other.weights != null) 95 | return false; 96 | } 97 | 98 | return weights.equals(other.weights); 99 | } 100 | 101 | //------------------------------------------------------------------------- 102 | 103 | } 104 | -------------------------------------------------------------------------------- /AI/src/decision_trees/classifiers/DecisionTreeNode.java: -------------------------------------------------------------------------------- 1 | package decision_trees.classifiers; 2 | 3 | import features.Feature; 4 | import features.FeatureVector; 5 | import features.feature_sets.BaseFeatureSet; 6 | 7 | /** 8 | * Abstract class for a node in a feature-based decision tree 9 | * that should output class probabilities. 10 | * 11 | * @author Dennis Soemers 12 | */ 13 | public abstract class DecisionTreeNode 14 | { 15 | 16 | //------------------------------------------------------------------------- 17 | 18 | /** 19 | * @param featureVector 20 | * @return Predicted (unnormalised) probability estimate for playing given feature vector 21 | */ 22 | public abstract float predict(final FeatureVector featureVector); 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** 27 | * Convert to tree in metadata format. 28 | * @return Decision tree node. 29 | */ 30 | public abstract metadata.ai.features.trees.classifiers.DecisionTreeNode toMetadataNode(); 31 | 32 | //------------------------------------------------------------------------- 33 | 34 | /** 35 | * Constructs a node (and hence, tree) from the given metadata node. 36 | * @param metadataNode 37 | * @param featureSet 38 | * @return Constructed node 39 | */ 40 | public static DecisionTreeNode fromMetadataNode 41 | ( 42 | final metadata.ai.features.trees.classifiers.DecisionTreeNode metadataNode, 43 | final BaseFeatureSet featureSet 44 | ) 45 | { 46 | if (metadataNode instanceof metadata.ai.features.trees.classifiers.If) 47 | { 48 | final metadata.ai.features.trees.classifiers.If ifNode = (metadata.ai.features.trees.classifiers.If) metadataNode; 49 | final DecisionTreeNode thenBranch = fromMetadataNode(ifNode.thenNode(), featureSet); 50 | final DecisionTreeNode elseBranch = fromMetadataNode(ifNode.elseNode(), featureSet); 51 | 52 | final String featureString = ifNode.featureString(); 53 | final int featureIdx = featureSet.findFeatureIndexForString(featureString); 54 | final Feature feature; 55 | if (featureIdx < featureSet.aspatialFeatures().length) 56 | { 57 | if (featureSet.aspatialFeatures()[featureIdx].toString().equals(featureString)) 58 | feature = featureSet.aspatialFeatures()[featureIdx]; 59 | else 60 | feature = featureSet.spatialFeatures()[featureIdx]; 61 | } 62 | else 63 | { 64 | feature = featureSet.spatialFeatures()[featureIdx]; 65 | } 66 | 67 | return new DecisionConditionNode(feature, thenBranch, elseBranch, featureIdx); 68 | } 69 | else if (metadataNode instanceof metadata.ai.features.trees.classifiers.BinaryLeaf) 70 | { 71 | final metadata.ai.features.trees.classifiers.BinaryLeaf leafNode = (metadata.ai.features.trees.classifiers.BinaryLeaf) metadataNode; 72 | return new BinaryLeafNode(leafNode.prob()); 73 | } 74 | else 75 | { 76 | final metadata.ai.features.trees.classifiers.Leaf leafNode = (metadata.ai.features.trees.classifiers.Leaf) metadataNode; 77 | return new DecisionLeafNode(leafNode.bottom25Prob(), leafNode.iqrProb(), leafNode.top25Prob()); 78 | } 79 | } 80 | 81 | //------------------------------------------------------------------------- 82 | 83 | } 84 | -------------------------------------------------------------------------------- /AI/src/decision_trees/logits/LogitDecisionNode.java: -------------------------------------------------------------------------------- 1 | package decision_trees.logits; 2 | 3 | import features.Feature; 4 | import features.FeatureVector; 5 | import features.aspatial.AspatialFeature; 6 | import metadata.ai.features.trees.logits.If; 7 | import metadata.ai.features.trees.logits.LogitNode; 8 | 9 | /** 10 | * Decision node in a feature-based logit tree 11 | * 12 | * @author Dennis Soemers 13 | */ 14 | public class LogitDecisionNode extends LogitTreeNode 15 | { 16 | 17 | //------------------------------------------------------------------------- 18 | 19 | /** The feature we want to evaluate (our condition) */ 20 | protected final Feature feature; 21 | 22 | /** Node we should traverse to if feature is true */ 23 | protected final LogitTreeNode trueNode; 24 | 25 | /** Node we should traverse to if feature is false */ 26 | protected final LogitTreeNode falseNode; 27 | 28 | /** Index of the feature we look at in our feature set (may index into either aspatial or spatial features list) */ 29 | protected int featureIdx = -1; 30 | 31 | //------------------------------------------------------------------------- 32 | 33 | /** 34 | * Constructor 35 | * @param feature 36 | * @param trueNode Node we should traverse to if feature is true 37 | * @param falseNode Node we should traverse to if feature is false 38 | */ 39 | public LogitDecisionNode 40 | ( 41 | final Feature feature, 42 | final LogitTreeNode trueNode, 43 | final LogitTreeNode falseNode 44 | ) 45 | { 46 | this.feature = feature; 47 | this.trueNode = trueNode; 48 | this.falseNode = falseNode; 49 | } 50 | 51 | /** 52 | * Constructor 53 | * @param feature 54 | * @param trueNode Node we should traverse to if feature is true 55 | * @param falseNode Node we should traverse to if feature is false 56 | * @param featureIdx Index of the feature 57 | */ 58 | public LogitDecisionNode 59 | ( 60 | final Feature feature, 61 | final LogitTreeNode trueNode, 62 | final LogitTreeNode falseNode, 63 | final int featureIdx 64 | ) 65 | { 66 | this.feature = feature; 67 | this.trueNode = trueNode; 68 | this.falseNode = falseNode; 69 | this.featureIdx = featureIdx; 70 | } 71 | 72 | //------------------------------------------------------------------------- 73 | 74 | @Override 75 | public float predict(final FeatureVector featureVector) 76 | { 77 | if (feature instanceof AspatialFeature) 78 | { 79 | if (featureVector.aspatialFeatureValues().get(featureIdx) != 0.f) 80 | return trueNode.predict(featureVector); 81 | else 82 | return falseNode.predict(featureVector); 83 | } 84 | else 85 | { 86 | if (featureVector.activeSpatialFeatureIndices().contains(featureIdx)) 87 | return trueNode.predict(featureVector); 88 | else 89 | return falseNode.predict(featureVector); 90 | } 91 | } 92 | 93 | //------------------------------------------------------------------------- 94 | 95 | @Override 96 | public LogitNode toMetadataNode() 97 | { 98 | return new If(feature.toString(), trueNode.toMetadataNode(), falseNode.toMetadataNode()); 99 | } 100 | 101 | //------------------------------------------------------------------------- 102 | 103 | } 104 | -------------------------------------------------------------------------------- /Features/src/features/feature_sets/network/Conjunction.java: -------------------------------------------------------------------------------- 1 | package features.feature_sets.network; 2 | 3 | import java.util.BitSet; 4 | 5 | /** 6 | * Represents a conjunction of atomic propositions. Is mutable: we can modify 7 | * it by giving it propositions that we "assume to have been proven", which will 8 | * then be removed from the requirements 9 | * 10 | * @author Dennis Soemers 11 | */ 12 | public class Conjunction 13 | { 14 | 15 | //------------------------------------------------------------------------- 16 | 17 | /** IDs of atomic propositions that must be true */ 18 | private final BitSet mustTrue; 19 | 20 | /** Number of propositions that are to be proven */ 21 | private int length; 22 | 23 | //------------------------------------------------------------------------- 24 | 25 | /** 26 | * Constructor 27 | * @param mustTrue 28 | */ 29 | public Conjunction(final BitSet mustTrue) 30 | { 31 | this.mustTrue = mustTrue; 32 | length = mustTrue.cardinality(); 33 | } 34 | 35 | //------------------------------------------------------------------------- 36 | 37 | /** 38 | * Tells this conjunction to assume that proposition of given ID is true 39 | * (safe to also call on conjunctions that do not require this proposition at all) 40 | * @param id 41 | * @return True if and only if the given proposition was a requirement of this conjunction 42 | */ 43 | public boolean assumeTrue(final int id) 44 | { 45 | if (mustTrue.get(id)) 46 | { 47 | mustTrue.clear(id); 48 | --length; 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | /** 56 | * @param other 57 | * @return True if, ignoring propositions that are already assumed to have been proven, 58 | * this conjunction generalises the given other conjunction. 59 | */ 60 | public boolean generalises(final Conjunction other) 61 | { 62 | if (length > other.length) 63 | return false; 64 | 65 | final BitSet otherToProve = other.toProve(); 66 | final BitSet toProve = (BitSet) mustTrue.clone(); 67 | toProve.andNot(otherToProve); 68 | return toProve.isEmpty(); 69 | } 70 | 71 | /** 72 | * @return Length of this conjunction: number of propositions remaining to be proven 73 | */ 74 | public int length() 75 | { 76 | return length; 77 | } 78 | 79 | /** 80 | * @return BitSet of IDs that must still be proven 81 | */ 82 | public BitSet toProve() 83 | { 84 | return mustTrue; 85 | } 86 | 87 | //------------------------------------------------------------------------- 88 | 89 | @Override 90 | public int hashCode() 91 | { 92 | final int prime = 31; 93 | int result = 1; 94 | result = prime * result + ((mustTrue == null) ? 0 : mustTrue.hashCode()); 95 | return result; 96 | } 97 | 98 | @Override 99 | public boolean equals(final Object obj) 100 | { 101 | if (this == obj) 102 | return true; 103 | 104 | if (!(obj instanceof Conjunction)) 105 | return false; 106 | 107 | final Conjunction other = (Conjunction) obj; 108 | return mustTrue.equals(other.mustTrue); 109 | } 110 | 111 | //------------------------------------------------------------------------- 112 | 113 | @Override 114 | public String toString() 115 | { 116 | return "[Conjunction: " + mustTrue + "]"; 117 | } 118 | 119 | //------------------------------------------------------------------------- 120 | 121 | } 122 | -------------------------------------------------------------------------------- /AI/src/search/flat/OnePlyNoHeuristic.java: -------------------------------------------------------------------------------- 1 | package search.flat; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.ThreadLocalRandom; 6 | 7 | import game.Game; 8 | import main.collections.FastArrayList; 9 | import other.AI; 10 | import other.RankUtils; 11 | import other.context.Context; 12 | import other.move.Move; 13 | 14 | /** 15 | * One-ply search with no heuristics (only optimises for best ranking achievable 16 | * in a single move, with random tie-breaking). For stochastic games, only randomly 17 | * considers one outcome for every move. 18 | * 19 | * @author Dennis Soemers 20 | */ 21 | public class OnePlyNoHeuristic extends AI 22 | { 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** The number of players in the game we're currently playing */ 27 | protected int numPlayersInGame = 0; 28 | 29 | //------------------------------------------------------------------------- 30 | 31 | /** 32 | * Constructor 33 | */ 34 | public OnePlyNoHeuristic() 35 | { 36 | this.friendlyName = "One-Ply (No Heuristic)"; 37 | } 38 | 39 | //------------------------------------------------------------------------- 40 | 41 | @Override 42 | public Move selectAction 43 | ( 44 | final Game game, 45 | final Context context, 46 | final double maxSeconds, 47 | final int maxIterations, 48 | final int maxDepth 49 | ) 50 | { 51 | final FastArrayList legalMoves = game.moves(context).moves(); 52 | final int agent = context.state().playerToAgent(context.state().mover()); 53 | 54 | double bestScore = Double.NEGATIVE_INFINITY; 55 | final List bestMoves = new ArrayList(); 56 | 57 | final double utilLowerBound = RankUtils.rankToUtil(context.computeNextLossRank(), numPlayersInGame); 58 | final double utilUpperBound = RankUtils.rankToUtil(context.computeNextWinRank(), numPlayersInGame); 59 | 60 | for (final Move move : legalMoves) 61 | { 62 | game.apply(context, move); 63 | final int player = context.state().currentPlayerOrder(agent); 64 | 65 | final double score; 66 | if (context.active(player)) 67 | { 68 | // Still active, so just assume average between lower and upper bound 69 | score = (utilLowerBound + utilUpperBound) / 2.0; 70 | } 71 | else 72 | { 73 | // Not active, so take actual utility 74 | score = RankUtils.rankToUtil(context.trial().ranking()[player], numPlayersInGame); 75 | } 76 | 77 | if (score > bestScore) 78 | bestMoves.clear(); 79 | 80 | if (score >= bestScore) 81 | { 82 | bestMoves.add(move); 83 | bestScore = score; 84 | } 85 | 86 | game.undo(context); 87 | } 88 | 89 | return bestMoves.get(ThreadLocalRandom.current().nextInt(bestMoves.size())); 90 | } 91 | 92 | //------------------------------------------------------------------------- 93 | 94 | @Override 95 | public void initAI(final Game game, final int playerID) 96 | { 97 | numPlayersInGame = game.players().count(); 98 | } 99 | 100 | @Override 101 | public boolean supportsGame(final Game game) 102 | { 103 | if (game.players().count() <= 1) 104 | return false; 105 | 106 | // if (game.isStochasticGame()) 107 | // return false; 108 | 109 | if (game.hiddenInformation()) 110 | return false; 111 | 112 | return game.isAlternatingMoveGame(); 113 | } 114 | 115 | //------------------------------------------------------------------------- 116 | 117 | } 118 | -------------------------------------------------------------------------------- /Features/src/features/FeatureVector.java: -------------------------------------------------------------------------------- 1 | package features; 2 | 3 | import gnu.trove.list.array.TIntArrayList; 4 | import main.collections.FVector; 5 | 6 | /** 7 | * Wrapper to represent a "vector" of features; internally, does not just hold a 8 | * single vector, but uses a sparse representation for binary (typically sparsely 9 | * active) spatial features, and a dense floats representation for aspatial features 10 | * (which are not necessarily binary). 11 | * 12 | * @author Dennis Soemers 13 | */ 14 | public class FeatureVector 15 | { 16 | 17 | //------------------------------------------------------------------------- 18 | 19 | /** Indices of spatial features that are active */ 20 | private final TIntArrayList activeSpatialFeatureIndices; 21 | 22 | /** Vector of values for aspatial features */ 23 | private final FVector aspatialFeatureValues; 24 | 25 | //------------------------------------------------------------------------- 26 | 27 | /** 28 | * Constructor 29 | * @param activeSpatialFeatureIndices 30 | * @param aspatialFeatureValues 31 | */ 32 | public FeatureVector(final TIntArrayList activeSpatialFeatureIndices, final FVector aspatialFeatureValues) 33 | { 34 | this.activeSpatialFeatureIndices = activeSpatialFeatureIndices; 35 | this.aspatialFeatureValues = aspatialFeatureValues; 36 | } 37 | 38 | //------------------------------------------------------------------------- 39 | 40 | /** 41 | * @return Indices of active spatial features (sparse representation) 42 | */ 43 | public TIntArrayList activeSpatialFeatureIndices() 44 | { 45 | return activeSpatialFeatureIndices; 46 | } 47 | 48 | /** 49 | * @return Vector of feature values for aspatial features (dense representation) 50 | */ 51 | public FVector aspatialFeatureValues() 52 | { 53 | return aspatialFeatureValues; 54 | } 55 | 56 | //------------------------------------------------------------------------- 57 | 58 | @Override 59 | public int hashCode() 60 | { 61 | final int prime = 31; 62 | int result = 1; 63 | result = prime * result + ((activeSpatialFeatureIndices == null) ? 0 : activeSpatialFeatureIndices.hashCode()); 64 | result = prime * result + ((aspatialFeatureValues == null) ? 0 : aspatialFeatureValues.hashCode()); 65 | return result; 66 | } 67 | 68 | @Override 69 | public boolean equals(final Object obj) 70 | { 71 | if (this == obj) 72 | return true; 73 | 74 | if (!(obj instanceof FeatureVector)) 75 | return false; 76 | 77 | final FeatureVector other = (FeatureVector) obj; 78 | if (activeSpatialFeatureIndices == null) 79 | { 80 | if (other.activeSpatialFeatureIndices != null) 81 | return false; 82 | } 83 | else if (!activeSpatialFeatureIndices.equals(other.activeSpatialFeatureIndices)) 84 | { 85 | return false; 86 | } 87 | 88 | if (aspatialFeatureValues == null) 89 | { 90 | if (other.aspatialFeatureValues != null) 91 | return false; 92 | } 93 | else if (!aspatialFeatureValues.equals(other.aspatialFeatureValues)) 94 | { 95 | return false; 96 | } 97 | 98 | return true; 99 | } 100 | 101 | //------------------------------------------------------------------------- 102 | 103 | @Override 104 | public String toString() 105 | { 106 | return ""; 107 | } 108 | 109 | //------------------------------------------------------------------------- 110 | 111 | } 112 | -------------------------------------------------------------------------------- /AI/src/training/expert_iteration/menageries/Menagerie.java: -------------------------------------------------------------------------------- 1 | package training.expert_iteration.menageries; 2 | 3 | import java.util.List; 4 | 5 | import game.Game; 6 | import metadata.ai.features.Features; 7 | import metadata.ai.heuristics.Heuristics; 8 | import other.context.Context; 9 | import training.expert_iteration.ExpertPolicy; 10 | import training.expert_iteration.params.AgentsParams; 11 | 12 | /** 13 | * Interface for "menageries": objects that can tell us which agents to use 14 | * in self-play games of a self-play training process. The term "menagier" is 15 | * inspired by "A Generalized Framework for Self-Play Training" by Hernandez et al., 2019. 16 | * However, in our case the menagerie actually also fulfills the roles of the 17 | * policy sampling distribution and the curator. 18 | * 19 | * @author Dennis Soemers 20 | */ 21 | public interface Menagerie 22 | { 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** 27 | * @param game 28 | * @param agentsParams 29 | * @return List of agents to use in a single self-play game 30 | */ 31 | public DrawnAgentsData drawAgents(final Game game, final AgentsParams agentsParams); 32 | 33 | //------------------------------------------------------------------------- 34 | 35 | /** 36 | * Initialise our population of checkpoints (+ dev) 37 | * 38 | * @param game 39 | * @param agentsParams 40 | * @param features 41 | * @param heuristics 42 | */ 43 | public void initialisePopulation 44 | ( 45 | final Game game, 46 | final AgentsParams agentsParams, 47 | final Features features, 48 | final Heuristics heuristics 49 | ); 50 | 51 | //------------------------------------------------------------------------- 52 | 53 | /** 54 | * Update the dev checkpoint's features 55 | * @param features 56 | */ 57 | public void updateDevFeatures(final Features features); 58 | 59 | /** 60 | * Update the dev checkpoint's heuristics 61 | * @param heuristics 62 | */ 63 | public void updateDevHeuristics(final Heuristics heuristics); 64 | 65 | /** 66 | * Update the menagerie based on an outcome of a self-play trial 67 | * @param context 68 | * @param drawnAgentsData 69 | */ 70 | public void updateOutcome(final Context context, final DrawnAgentsData drawnAgentsData); 71 | 72 | //------------------------------------------------------------------------- 73 | 74 | /** 75 | * @return String describing the menagerie's data, for log (or null if nothing to log) 76 | */ 77 | public String generateLog(); 78 | 79 | //------------------------------------------------------------------------- 80 | 81 | /** 82 | * Data describing a collection of agents that has been drawn. Specific 83 | * implementations of Menageries may use subclasses of this to include 84 | * additional data that they might need. 85 | * 86 | * @author Dennis Soemers 87 | */ 88 | public static class DrawnAgentsData 89 | { 90 | 91 | /** List of experts */ 92 | protected final List agents; 93 | 94 | /** 95 | * Constructor 96 | * @param agents 97 | */ 98 | public DrawnAgentsData(final List agents) 99 | { 100 | this.agents = agents; 101 | } 102 | 103 | /** 104 | * @return List of expert agents 105 | */ 106 | public List getAgents() 107 | { 108 | return agents; 109 | } 110 | 111 | } 112 | 113 | //------------------------------------------------------------------------- 114 | 115 | } 116 | -------------------------------------------------------------------------------- /AI/src/utils/LudiiAI.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | import game.Game; 3 | import other.AI; 4 | import other.context.Context; 5 | import other.move.Move; 6 | 7 | /** 8 | * Default Ludii AI. This is an agent that attempts to automatically 9 | * switch to different algorithms based on the metadata in a game's 10 | * .lud file. 11 | * 12 | * If no best AI can be discovered from the metadata, this will default to: 13 | * - Flat Monte-Carlo for simultaneous-move games 14 | * - UCT for everything else 15 | * 16 | * @author Dennis Soemers 17 | */ 18 | public final class LudiiAI extends AI 19 | { 20 | 21 | //------------------------------------------------------------------------- 22 | 23 | /** The current agent we use for the current game */ 24 | private AI currentAgent = null; 25 | 26 | //------------------------------------------------------------------------- 27 | 28 | /** 29 | * Constructor 30 | */ 31 | public LudiiAI() 32 | { 33 | this.friendlyName = "Ludii"; 34 | } 35 | 36 | //------------------------------------------------------------------------- 37 | 38 | @Override 39 | public Move selectAction 40 | ( 41 | final Game game, 42 | final Context context, 43 | final double maxSeconds, 44 | final int maxIterations, 45 | final int maxDepth 46 | ) 47 | { 48 | return currentAgent.selectAction(game, context, maxSeconds, maxIterations, maxDepth); 49 | } 50 | 51 | //------------------------------------------------------------------------- 52 | 53 | @Override 54 | public void initAI(final Game game, final int playerID) 55 | { 56 | if (currentAgent != null) 57 | currentAgent.closeAI(); 58 | 59 | currentAgent = AIFactory.fromMetadata(game); 60 | 61 | if (currentAgent == null) 62 | { 63 | if (!game.isAlternatingMoveGame()) 64 | currentAgent = AIFactory.createAI("Flat MC"); 65 | else 66 | currentAgent = AIFactory.createAI("UCT"); 67 | } 68 | 69 | if (!currentAgent.supportsGame(game)) 70 | { 71 | System.err.println 72 | ( 73 | "Warning! Default AI (" + currentAgent + ")" 74 | + " does not support game (" + game.name() + ")" 75 | ); 76 | 77 | currentAgent = AIFactory.createAI("UCT"); 78 | } 79 | 80 | assert(currentAgent.supportsGame(game)); 81 | 82 | this.friendlyName = "Ludii (" + currentAgent.friendlyName() + ")"; 83 | 84 | currentAgent.initAI(game, playerID); 85 | } 86 | 87 | @Override 88 | public boolean supportsGame(final Game game) 89 | { 90 | return true; 91 | } 92 | 93 | @Override 94 | public double estimateValue() 95 | { 96 | if (currentAgent != null) 97 | return currentAgent.estimateValue(); 98 | else 99 | return 0.0; 100 | } 101 | 102 | @Override 103 | public String generateAnalysisReport() 104 | { 105 | if (currentAgent != null) 106 | return currentAgent.generateAnalysisReport(); 107 | else 108 | return null; 109 | } 110 | 111 | @Override 112 | public AIVisualisationData aiVisualisationData() 113 | { 114 | if (currentAgent != null) 115 | return currentAgent.aiVisualisationData(); 116 | else 117 | return null; 118 | } 119 | 120 | @Override 121 | public void setWantsInterrupt(final boolean val) 122 | { 123 | super.setWantsInterrupt(val); 124 | if (currentAgent != null) 125 | currentAgent.setWantsInterrupt(val); 126 | } 127 | 128 | @Override 129 | public boolean usesFeatures(final Game game) 130 | { 131 | return AIFactory.fromMetadata(game).usesFeatures(game); 132 | } 133 | 134 | //------------------------------------------------------------------------- 135 | 136 | } 137 | -------------------------------------------------------------------------------- /AI/src/playout_move_selectors/FeaturesSoftmaxMoveSelector.java: -------------------------------------------------------------------------------- 1 | package playout_move_selectors; 2 | 3 | import features.FeatureVector; 4 | import features.WeightVector; 5 | import features.feature_sets.BaseFeatureSet; 6 | import main.collections.FVector; 7 | import main.collections.FastArrayList; 8 | import other.context.Context; 9 | import other.move.Move; 10 | import other.playout.PlayoutMoveSelector; 11 | 12 | /** 13 | * PlayoutMoveSelector for playouts which uses a softmax over actions with logits 14 | * computed by features. 15 | * 16 | * @author Dennis Soemers 17 | */ 18 | public class FeaturesSoftmaxMoveSelector extends PlayoutMoveSelector // TODO also a greedy version? 19 | { 20 | 21 | //------------------------------------------------------------------------- 22 | 23 | /** Feature sets (one per player, or just a shared one at index 0) */ 24 | protected final BaseFeatureSet[] featureSets; 25 | 26 | /** Weight vectors (one per player, or just a shared one at index 0) */ 27 | protected final WeightVector[] weights; 28 | 29 | /** Do we want to use thresholding to ignore low-weight features? */ 30 | protected final boolean thresholded; 31 | 32 | //------------------------------------------------------------------------- 33 | 34 | /** 35 | * Constructor 36 | * @param featureSets Feature sets (one per player, or just a shared one at index 0) 37 | * @param weights Weight vectors (one per player, or just a shared one at index 0) 38 | * @param thresholded Do we want to use thresholding to ignore low-weight features? 39 | */ 40 | public FeaturesSoftmaxMoveSelector 41 | ( 42 | final BaseFeatureSet[] featureSets, 43 | final WeightVector[] weights, 44 | final boolean thresholded 45 | ) 46 | { 47 | this.featureSets = featureSets; 48 | this.weights = weights; 49 | this.thresholded = thresholded; 50 | } 51 | 52 | //------------------------------------------------------------------------- 53 | 54 | @Override 55 | public Move selectMove 56 | ( 57 | final Context context, 58 | final FastArrayList maybeLegalMoves, 59 | final int p, 60 | final IsMoveReallyLegal isMoveReallyLegal 61 | ) 62 | { 63 | final BaseFeatureSet featureSet; 64 | final WeightVector weightVector; 65 | if (featureSets.length == 1) 66 | { 67 | featureSet = featureSets[0]; 68 | weightVector = weights[0]; 69 | } 70 | else 71 | { 72 | featureSet = featureSets[p]; 73 | weightVector = weights[p]; 74 | } 75 | 76 | final FeatureVector[] featureVectors = featureSet.computeFeatureVectors(context, maybeLegalMoves, thresholded); 77 | 78 | final float[] logits = new float[featureVectors.length]; 79 | 80 | for (int i = 0; i < featureVectors.length; ++i) 81 | { 82 | logits[i] = weightVector.dot(featureVectors[i]); 83 | } 84 | 85 | final FVector distribution = FVector.wrap(logits); 86 | distribution.softmax(); 87 | 88 | int numLegalMoves = maybeLegalMoves.size(); 89 | 90 | while (numLegalMoves > 0) 91 | { 92 | --numLegalMoves; // We're trying a move; if this one fails, it's actually not legal 93 | 94 | final int n = distribution.sampleFromDistribution(); 95 | final Move move = maybeLegalMoves.get(n); 96 | 97 | if (isMoveReallyLegal.checkMove(move)) 98 | return move; // Only return this move if it's really legal 99 | else 100 | distribution.updateSoftmaxInvalidate(n); // Incrementally update the softmax, move n is invalid 101 | } 102 | 103 | // No legal moves? 104 | return null; 105 | } 106 | 107 | //------------------------------------------------------------------------- 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Features/src/features/spatial/cache/footprints/FullFootprint.java: -------------------------------------------------------------------------------- 1 | package features.spatial.cache.footprints; 2 | 3 | import main.collections.ChunkSet; 4 | 5 | /** 6 | * Footprint implementation with support for a mix of cell/vertex/edge stuff. 7 | * 8 | * @author Dennis Soemers 9 | */ 10 | public class FullFootprint extends BaseFootprint 11 | { 12 | 13 | //------------------------------------------------------------------------- 14 | 15 | /** Mask for all chunks that we run at least one "empty" cell test on */ 16 | protected final ChunkSet emptyCell; 17 | /** Mask for all chunks that we run at least one "empty" vertex test on */ 18 | protected final ChunkSet emptyVertex; 19 | /** Mask for all chunks that we run at least one "empty" edge test on */ 20 | protected final ChunkSet emptyEdge; 21 | 22 | /** Mask for all chunks that we run at least one "who" cell test on */ 23 | protected final ChunkSet whoCell; 24 | /** Mask for all chunks that we run at least one "who" vertex test on */ 25 | protected final ChunkSet whoVertex; 26 | /** Mask for all chunks that we run at least one "who" edge test on */ 27 | protected final ChunkSet whoEdge; 28 | 29 | /** Mask for all chunks that we run at least one "what" cell test on */ 30 | protected final ChunkSet whatCell; 31 | /** Mask for all chunks that we run at least one "what" vertex test on */ 32 | protected final ChunkSet whatVertex; 33 | /** Mask for all chunks that we run at least one "what" edge test on */ 34 | protected final ChunkSet whatEdge; 35 | 36 | //------------------------------------------------------------------------- 37 | 38 | /** 39 | * Constructor 40 | * @param emptyCell 41 | * @param emptyVertex 42 | * @param emptyEdge 43 | * @param whoCell 44 | * @param whoVertex 45 | * @param whoEdge 46 | * @param whatCell 47 | * @param whatVertex 48 | * @param whatEdge 49 | */ 50 | public FullFootprint 51 | ( 52 | final ChunkSet emptyCell, 53 | final ChunkSet emptyVertex, 54 | final ChunkSet emptyEdge, 55 | final ChunkSet whoCell, 56 | final ChunkSet whoVertex, 57 | final ChunkSet whoEdge, 58 | final ChunkSet whatCell, 59 | final ChunkSet whatVertex, 60 | final ChunkSet whatEdge 61 | ) 62 | { 63 | this.emptyCell = emptyCell; 64 | this.emptyVertex = emptyVertex; 65 | this.emptyEdge = emptyEdge; 66 | this.whoCell = whoCell; 67 | this.whoVertex = whoVertex; 68 | this.whoEdge = whoEdge; 69 | this.whatCell = whatCell; 70 | this.whatVertex = whatVertex; 71 | this.whatEdge = whatEdge; 72 | } 73 | 74 | //------------------------------------------------------------------------- 75 | 76 | @Override 77 | public ChunkSet emptyCell() 78 | { 79 | return emptyCell; 80 | } 81 | 82 | @Override 83 | public ChunkSet emptyVertex() 84 | { 85 | return emptyVertex; 86 | } 87 | 88 | @Override 89 | public ChunkSet emptyEdge() 90 | { 91 | return emptyEdge; 92 | } 93 | 94 | @Override 95 | public ChunkSet whoCell() 96 | { 97 | return whoCell; 98 | } 99 | 100 | @Override 101 | public ChunkSet whoVertex() 102 | { 103 | return whoVertex; 104 | } 105 | 106 | @Override 107 | public ChunkSet whoEdge() 108 | { 109 | return whoEdge; 110 | } 111 | 112 | @Override 113 | public ChunkSet whatCell() 114 | { 115 | return whatCell; 116 | } 117 | 118 | @Override 119 | public ChunkSet whatVertex() 120 | { 121 | return whatVertex; 122 | } 123 | 124 | @Override 125 | public ChunkSet whatEdge() 126 | { 127 | return whatEdge; 128 | } 129 | 130 | //------------------------------------------------------------------------- 131 | 132 | } 133 | -------------------------------------------------------------------------------- /AI/src/playout_move_selectors/DecisionTreeMoveSelector.java: -------------------------------------------------------------------------------- 1 | package playout_move_selectors; 2 | 3 | import decision_trees.classifiers.DecisionTreeNode; 4 | import features.FeatureVector; 5 | import features.feature_sets.BaseFeatureSet; 6 | import main.collections.FVector; 7 | import main.collections.FastArrayList; 8 | import other.context.Context; 9 | import other.move.Move; 10 | import other.playout.PlayoutMoveSelector; 11 | 12 | /** 13 | * PlayoutMoveSelector for playouts which uses a distribution over actions 14 | * computed by move-classification feature trees. 15 | * 16 | * @author Dennis Soemers 17 | */ 18 | public class DecisionTreeMoveSelector extends PlayoutMoveSelector 19 | { 20 | 21 | //------------------------------------------------------------------------- 22 | 23 | /** Feature sets (one per player, or just a shared one at index 0) */ 24 | protected final BaseFeatureSet[] featureSets; 25 | 26 | /** Classification tree root nodes (one per player, or just a shared one at index 0) */ 27 | protected final DecisionTreeNode[] rootNodes; 28 | 29 | /** Do we want to play greedily? */ 30 | protected final boolean greedy; 31 | 32 | //------------------------------------------------------------------------- 33 | 34 | /** 35 | * Constructor 36 | * @param featureSets Feature sets (one per player, or just a shared one at index 0) 37 | * @param rootNodes Classification tree root nodes (one per player, or just a shared one at index 0) 38 | * @param greedy Do we want to play greedily? 39 | */ 40 | public DecisionTreeMoveSelector 41 | ( 42 | final BaseFeatureSet[] featureSets, 43 | final DecisionTreeNode[] rootNodes, 44 | final boolean greedy 45 | ) 46 | { 47 | this.featureSets = featureSets; 48 | this.rootNodes = rootNodes; 49 | this.greedy = greedy; 50 | } 51 | 52 | //------------------------------------------------------------------------- 53 | 54 | @Override 55 | public Move selectMove 56 | ( 57 | final Context context, 58 | final FastArrayList maybeLegalMoves, 59 | final int p, 60 | final IsMoveReallyLegal isMoveReallyLegal 61 | ) 62 | { 63 | final BaseFeatureSet featureSet; 64 | final DecisionTreeNode rootNode; 65 | if (featureSets.length == 1) 66 | { 67 | featureSet = featureSets[0]; 68 | rootNode = rootNodes[0]; 69 | } 70 | else 71 | { 72 | featureSet = featureSets[p]; 73 | rootNode = rootNodes[p]; 74 | } 75 | 76 | final FeatureVector[] featureVectors = featureSet.computeFeatureVectors(context, maybeLegalMoves, false); 77 | 78 | final float[] unnormalisedProbs = new float[featureVectors.length]; 79 | 80 | for (int i = 0; i < featureVectors.length; ++i) 81 | { 82 | unnormalisedProbs[i] = rootNode.predict(featureVectors[i]); 83 | } 84 | 85 | final FVector distribution = FVector.wrap(unnormalisedProbs); 86 | distribution.normalise(); 87 | 88 | int numLegalMoves = maybeLegalMoves.size(); 89 | 90 | while (numLegalMoves > 0) 91 | { 92 | --numLegalMoves; // We're trying a move; if this one fails, it's actually not legal 93 | 94 | final int n = (greedy) ? distribution.argMaxRand() : distribution.sampleFromDistribution(); 95 | final Move move = maybeLegalMoves.get(n); 96 | 97 | if (isMoveReallyLegal.checkMove(move)) 98 | return move; // Only return this move if it's really legal 99 | else 100 | distribution.updateSoftmaxInvalidate(n); // Incrementally update the softmax, move n is invalid 101 | } 102 | 103 | // No legal moves? 104 | return null; 105 | } 106 | 107 | //------------------------------------------------------------------------- 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Features/src/features/spatial/instances/OneOfMustEmpty.java: -------------------------------------------------------------------------------- 1 | package features.spatial.instances; 2 | 3 | import game.types.board.SiteType; 4 | import main.collections.ChunkSet; 5 | import other.state.State; 6 | import other.state.container.ContainerState; 7 | 8 | /** 9 | * Simultaneously tests multiple chunks of the "empty" ChunkSet, returning 10 | * true if at least one of them is indeed empty. 11 | * 12 | * TODO could make special cases of this class for cells, vertices, and edges 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public final class OneOfMustEmpty implements BitwiseTest 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** Set of chunks of which at least one must be empty for test to succeed */ 22 | protected final ChunkSet mustEmpties; 23 | 24 | /** The first non-zero word in the mustEmpties ChunkSet */ 25 | protected final int firstUsedWord; 26 | 27 | /** Graph element type we want to test on */ 28 | protected final SiteType graphElementType; 29 | 30 | //------------------------------------------------------------------------- 31 | 32 | /** 33 | * Constructor 34 | * @param mustEmpties 35 | * @param graphElementType 36 | */ 37 | public OneOfMustEmpty(final ChunkSet mustEmpties, final SiteType graphElementType) 38 | { 39 | this.mustEmpties = mustEmpties; 40 | this.graphElementType = graphElementType; 41 | firstUsedWord = mustEmpties.nextSetBit(0) / Long.SIZE; 42 | } 43 | 44 | //------------------------------------------------------------------------- 45 | 46 | @Override 47 | public final boolean matches(final State state) 48 | { 49 | final ContainerState container = state.containerStates()[0]; 50 | final ChunkSet chunkSet; 51 | switch (graphElementType) 52 | { 53 | case Cell: 54 | chunkSet = container.emptyChunkSetCell(); 55 | break; 56 | case Vertex: 57 | chunkSet = container.emptyChunkSetVertex(); 58 | break; 59 | case Edge: 60 | chunkSet = container.emptyChunkSetEdge(); 61 | break; 62 | //$CASES-OMITTED$ Hint 63 | default: 64 | chunkSet = null; 65 | break; 66 | } 67 | 68 | return chunkSet.violatesNot(mustEmpties, mustEmpties, firstUsedWord); 69 | } 70 | 71 | //------------------------------------------------------------------------- 72 | 73 | @Override 74 | public final boolean hasNoTests() 75 | { 76 | return false; 77 | } 78 | 79 | @Override 80 | public final boolean onlyRequiresSingleMustEmpty() 81 | { 82 | return true; 83 | } 84 | 85 | @Override 86 | public final boolean onlyRequiresSingleMustWho() 87 | { 88 | return false; 89 | } 90 | 91 | @Override 92 | public final boolean onlyRequiresSingleMustWhat() 93 | { 94 | return false; 95 | } 96 | 97 | @Override 98 | public final SiteType graphElementType() 99 | { 100 | return graphElementType; 101 | } 102 | 103 | //------------------------------------------------------------------------- 104 | 105 | /** 106 | * @return Chunks of which at least one must be empty 107 | */ 108 | public final ChunkSet mustEmpties() 109 | { 110 | return mustEmpties; 111 | } 112 | 113 | //------------------------------------------------------------------------- 114 | 115 | @Override 116 | public String toString() 117 | { 118 | String requirementsStr = ""; 119 | 120 | for (int i = mustEmpties.nextSetBit(0); 121 | i >= 0; i = mustEmpties.nextSetBit(i + 1)) 122 | { 123 | requirementsStr += i + ", "; 124 | } 125 | 126 | return String.format( 127 | "One of these must be empty: [%s]", 128 | requirementsStr); 129 | } 130 | 131 | //------------------------------------------------------------------------- 132 | 133 | } 134 | -------------------------------------------------------------------------------- /AI/src/decision_trees/logits/LogitTreeNode.java: -------------------------------------------------------------------------------- 1 | package decision_trees.logits; 2 | 3 | import features.Feature; 4 | import features.FeatureVector; 5 | import features.feature_sets.BaseFeatureSet; 6 | import metadata.ai.features.trees.logits.LogitNode; 7 | 8 | /** 9 | * Abstract class for a node in a feature-based regression tree 10 | * that should output logits. 11 | * 12 | * @author Dennis Soemers 13 | */ 14 | public abstract class LogitTreeNode 15 | { 16 | 17 | //------------------------------------------------------------------------- 18 | 19 | /** 20 | * @param featureVector 21 | * @return Predicted logit for given feature vector 22 | */ 23 | public abstract float predict(final FeatureVector featureVector); 24 | 25 | //------------------------------------------------------------------------- 26 | 27 | /** 28 | * Convert to tree in metadata format. 29 | * @return logit node. 30 | */ 31 | public abstract LogitNode toMetadataNode(); 32 | 33 | //------------------------------------------------------------------------- 34 | 35 | /** 36 | * Constructs a node (and hence, tree) from the given metadata node. 37 | * @param metadataNode 38 | * @param featureSet 39 | * @return Constructed node 40 | */ 41 | public static LogitTreeNode fromMetadataNode(final LogitNode metadataNode, final BaseFeatureSet featureSet) 42 | { 43 | if (metadataNode instanceof metadata.ai.features.trees.logits.If) 44 | { 45 | final metadata.ai.features.trees.logits.If ifNode = (metadata.ai.features.trees.logits.If) metadataNode; 46 | final LogitTreeNode thenBranch = fromMetadataNode(ifNode.thenNode(), featureSet); 47 | final LogitTreeNode elseBranch = fromMetadataNode(ifNode.elseNode(), featureSet); 48 | 49 | final String featureString = ifNode.featureString(); 50 | final int featureIdx = featureSet.findFeatureIndexForString(featureString); 51 | final Feature feature; 52 | if (featureIdx < featureSet.aspatialFeatures().length) 53 | { 54 | if (featureSet.aspatialFeatures()[featureIdx].toString().equals(featureString)) 55 | feature = featureSet.aspatialFeatures()[featureIdx]; 56 | else 57 | feature = featureSet.spatialFeatures()[featureIdx]; 58 | } 59 | else 60 | { 61 | feature = featureSet.spatialFeatures()[featureIdx]; 62 | } 63 | 64 | return new LogitDecisionNode(feature, thenBranch, elseBranch, featureIdx); 65 | } 66 | else 67 | { 68 | final metadata.ai.features.trees.logits.Leaf leafNode = (metadata.ai.features.trees.logits.Leaf) metadataNode; 69 | 70 | final String[] featureStrings = leafNode.featureStrings(); 71 | final float[] weights = leafNode.weights(); 72 | final int[] featureIndices = new int[featureStrings.length]; 73 | 74 | final Feature[] features = new Feature[featureStrings.length]; 75 | for (int i = 0; i < features.length; ++i) 76 | { 77 | final String featureString = featureStrings[i]; 78 | final int featureIdx = featureSet.findFeatureIndexForString(featureString); 79 | final Feature feature; 80 | if (featureIdx < featureSet.aspatialFeatures().length) 81 | { 82 | if (featureSet.aspatialFeatures()[featureIdx].toString().equals(featureString)) 83 | feature = featureSet.aspatialFeatures()[featureIdx]; 84 | else 85 | feature = featureSet.spatialFeatures()[featureIdx]; 86 | } 87 | else 88 | { 89 | feature = featureSet.spatialFeatures()[featureIdx]; 90 | } 91 | 92 | features[i] = feature; 93 | featureIndices[i] = featureIdx; 94 | } 95 | 96 | return new LogitModelNode(features, weights, featureIndices); 97 | } 98 | } 99 | 100 | //------------------------------------------------------------------------- 101 | 102 | } 103 | -------------------------------------------------------------------------------- /AI/src/playout_move_selectors/LogitTreeMoveSelector.java: -------------------------------------------------------------------------------- 1 | package playout_move_selectors; 2 | 3 | import decision_trees.logits.LogitTreeNode; 4 | import features.FeatureVector; 5 | import features.feature_sets.BaseFeatureSet; 6 | import main.collections.FVector; 7 | import main.collections.FastArrayList; 8 | import other.context.Context; 9 | import other.move.Move; 10 | import other.playout.PlayoutMoveSelector; 11 | 12 | /** 13 | * PlayoutMoveSelector for playouts which uses a softmax over actions with logits 14 | * computed by feature regression trees. 15 | * 16 | * @author Dennis Soemers 17 | */ 18 | public class LogitTreeMoveSelector extends PlayoutMoveSelector 19 | { 20 | 21 | //------------------------------------------------------------------------- 22 | 23 | /** Feature sets (one per player, or just a shared one at index 0) */ 24 | protected final BaseFeatureSet[] featureSets; 25 | 26 | /** Regression tree root nodes (one per player, or just a shared one at index 0) */ 27 | protected final LogitTreeNode[] rootNodes; 28 | 29 | /** Do we want to play greedily? */ 30 | protected final boolean greedy; 31 | 32 | /** Temperature for the distribution */ 33 | protected final double temperature; 34 | 35 | //------------------------------------------------------------------------- 36 | 37 | /** 38 | * Constructor 39 | * @param featureSets Feature sets (one per player, or just a shared one at index 0) 40 | * @param rootNodes Regression tree root nodes (one per player, or just a shared one at index 0) 41 | * @param greedy Do we want to play greedily? 42 | * @param temperature 43 | */ 44 | public LogitTreeMoveSelector 45 | ( 46 | final BaseFeatureSet[] featureSets, 47 | final LogitTreeNode[] rootNodes, 48 | final boolean greedy, 49 | final double temperature 50 | ) 51 | { 52 | this.featureSets = featureSets; 53 | this.rootNodes = rootNodes; 54 | this.greedy = greedy; 55 | this.temperature = temperature; 56 | } 57 | 58 | //------------------------------------------------------------------------- 59 | 60 | @Override 61 | public Move selectMove 62 | ( 63 | final Context context, 64 | final FastArrayList maybeLegalMoves, 65 | final int p, 66 | final IsMoveReallyLegal isMoveReallyLegal 67 | ) 68 | { 69 | final BaseFeatureSet featureSet; 70 | final LogitTreeNode rootNode; 71 | if (featureSets.length == 1) 72 | { 73 | featureSet = featureSets[0]; 74 | rootNode = rootNodes[0]; 75 | } 76 | else 77 | { 78 | featureSet = featureSets[p]; 79 | rootNode = rootNodes[p]; 80 | } 81 | 82 | final FeatureVector[] featureVectors = featureSet.computeFeatureVectors(context, maybeLegalMoves, false); 83 | 84 | final float[] logits = new float[featureVectors.length]; 85 | 86 | for (int i = 0; i < featureVectors.length; ++i) 87 | { 88 | logits[i] = rootNode.predict(featureVectors[i]); 89 | } 90 | 91 | final FVector distribution = FVector.wrap(logits); 92 | distribution.softmax(temperature); 93 | 94 | int numLegalMoves = maybeLegalMoves.size(); 95 | 96 | while (numLegalMoves > 0) 97 | { 98 | --numLegalMoves; // We're trying a move; if this one fails, it's actually not legal 99 | 100 | final int n = greedy ? distribution.argMaxRand() : distribution.sampleFromDistribution(); 101 | final Move move = maybeLegalMoves.get(n); 102 | 103 | if (isMoveReallyLegal.checkMove(move)) 104 | return move; // Only return this move if it's really legal 105 | else 106 | distribution.updateSoftmaxInvalidate(n); // Incrementally update the softmax, move n is invalid 107 | } 108 | 109 | // No legal moves? 110 | return null; 111 | } 112 | 113 | //------------------------------------------------------------------------- 114 | 115 | } 116 | -------------------------------------------------------------------------------- /AI/src/optimisers/OptimiserFactory.java: -------------------------------------------------------------------------------- 1 | package optimisers; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.net.URL; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Can create Optimizers based on strings / files 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public class OptimiserFactory 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** 22 | * Constructor should not be used. 23 | */ 24 | private OptimiserFactory() 25 | { 26 | // not intended to be used 27 | } 28 | 29 | //------------------------------------------------------------------------- 30 | 31 | /** 32 | * @param string String representation of optimizer, 33 | * or filename from which to load optimizer 34 | * 35 | * @return Created AI 36 | */ 37 | public static Optimiser createOptimiser(final String string) 38 | { 39 | if (string.equalsIgnoreCase("SGD")) 40 | { 41 | return new SGD(0.05f); 42 | } 43 | else if (string.equalsIgnoreCase("RMSProp")) 44 | { 45 | return new DeepmindRMSProp(); 46 | } 47 | else if (string.equalsIgnoreCase("AMSGrad")) 48 | { 49 | // the Karpathy constant: 50 | // https://twitter.com/karpathy/status/801621764144971776 51 | return new AMSGrad(3E-4f); 52 | } 53 | 54 | // try to interpret the given string as a resource or some other 55 | // kind of file 56 | final URL optimiserURL = OptimiserFactory.class.getResource(string); 57 | File optimiserFile = null; 58 | 59 | if (optimiserURL != null) 60 | { 61 | optimiserFile = new File(optimiserURL.getFile()); 62 | } 63 | else 64 | { 65 | optimiserFile = new File(string); 66 | } 67 | 68 | String[] lines = new String[0]; 69 | 70 | if (optimiserFile.exists()) 71 | { 72 | try (BufferedReader reader = new BufferedReader( 73 | new FileReader(optimiserFile))) 74 | { 75 | final List linesList = new ArrayList(); 76 | 77 | String line = reader.readLine(); 78 | while (line != null) 79 | { 80 | linesList.add(line); 81 | } 82 | 83 | lines = linesList.toArray(lines); 84 | } 85 | catch (final IOException e) 86 | { 87 | e.printStackTrace(); 88 | } 89 | } 90 | else 91 | { 92 | // assume semicolon-separated lines directly passed as 93 | // command line arg 94 | lines = string.split(";"); 95 | } 96 | 97 | final String firstLine = lines[0]; 98 | if (firstLine.startsWith("optimiser=")) 99 | { 100 | final String optimiserName = 101 | firstLine.substring("optimiser=".length()); 102 | 103 | if (optimiserName.equalsIgnoreCase("SGD")) 104 | { 105 | // UCT is the default implementation of MCTS, 106 | // so both cases are the same 107 | return SGD.fromLines(lines); 108 | } 109 | else if (optimiserName.equalsIgnoreCase("RMSProp")) 110 | { 111 | return DeepmindRMSProp.fromLines(lines); 112 | } 113 | else if (optimiserName.equalsIgnoreCase("AMSGrad")) 114 | { 115 | return AMSGrad.fromLines(lines); 116 | } 117 | else 118 | { 119 | System.err.println("Unknown optimizer name: " + optimiserName); 120 | } 121 | } 122 | else 123 | { 124 | System.err.println( 125 | "Expecting Optimizer file to start with \"optimiser=\", " 126 | + "but it starts with " + firstLine); 127 | } 128 | 129 | System.err.println(String.format( 130 | "Warning: cannot convert string \"%s\" to Optimiser; " 131 | + "defaulting to vanilla SGD.", 132 | string)); 133 | return new SGD(0.05f); 134 | } 135 | 136 | //------------------------------------------------------------------------- 137 | 138 | } 139 | -------------------------------------------------------------------------------- /AI/src/decision_trees/classifiers/DecisionConditionNode.java: -------------------------------------------------------------------------------- 1 | package decision_trees.classifiers; 2 | 3 | import features.Feature; 4 | import features.FeatureVector; 5 | import features.aspatial.AspatialFeature; 6 | import metadata.ai.features.trees.classifiers.If; 7 | 8 | /** 9 | * Decision node in a feature-based logit tree 10 | * 11 | * @author Dennis Soemers 12 | */ 13 | public class DecisionConditionNode extends DecisionTreeNode 14 | { 15 | 16 | //------------------------------------------------------------------------- 17 | 18 | /** The feature we want to evaluate (our condition) */ 19 | protected final Feature feature; 20 | 21 | /** Node we should traverse to if feature is true */ 22 | protected final DecisionTreeNode trueNode; 23 | 24 | /** Node we should traverse to if feature is false */ 25 | protected final DecisionTreeNode falseNode; 26 | 27 | /** Index of the feature we look at in our feature set (may index into either aspatial or spatial features list) */ 28 | protected int featureIdx = -1; 29 | 30 | //------------------------------------------------------------------------- 31 | 32 | /** 33 | * Constructor 34 | * @param feature 35 | * @param trueNode Node we should traverse to if feature is true 36 | * @param falseNode Node we should traverse to if feature is false 37 | */ 38 | public DecisionConditionNode 39 | ( 40 | final Feature feature, 41 | final DecisionTreeNode trueNode, 42 | final DecisionTreeNode falseNode 43 | ) 44 | { 45 | this.feature = feature; 46 | this.trueNode = trueNode; 47 | this.falseNode = falseNode; 48 | } 49 | 50 | /** 51 | * Constructor 52 | * @param feature 53 | * @param trueNode Node we should traverse to if feature is true 54 | * @param falseNode Node we should traverse to if feature is false 55 | * @param featureIdx Index of the feature 56 | */ 57 | public DecisionConditionNode 58 | ( 59 | final Feature feature, 60 | final DecisionTreeNode trueNode, 61 | final DecisionTreeNode falseNode, 62 | final int featureIdx 63 | ) 64 | { 65 | this.feature = feature; 66 | this.trueNode = trueNode; 67 | this.falseNode = falseNode; 68 | this.featureIdx = featureIdx; 69 | } 70 | 71 | //------------------------------------------------------------------------- 72 | 73 | @Override 74 | public float predict(final FeatureVector featureVector) 75 | { 76 | if (feature instanceof AspatialFeature) 77 | { 78 | if (featureVector.aspatialFeatureValues().get(featureIdx) != 0.f) 79 | return trueNode.predict(featureVector); 80 | else 81 | return falseNode.predict(featureVector); 82 | } 83 | else 84 | { 85 | if (featureVector.activeSpatialFeatureIndices().contains(featureIdx)) 86 | return trueNode.predict(featureVector); 87 | else 88 | return falseNode.predict(featureVector); 89 | } 90 | } 91 | 92 | //------------------------------------------------------------------------- 93 | 94 | @Override 95 | public metadata.ai.features.trees.classifiers.DecisionTreeNode toMetadataNode() 96 | { 97 | return new If(feature.toString(), trueNode.toMetadataNode(), falseNode.toMetadataNode()); 98 | } 99 | 100 | //------------------------------------------------------------------------- 101 | 102 | /** 103 | * @return The feature we check in this node 104 | */ 105 | public Feature feature() 106 | { 107 | return feature; 108 | } 109 | 110 | /** 111 | * @return The node we go to when the feature is active 112 | */ 113 | public DecisionTreeNode trueNode() 114 | { 115 | return trueNode; 116 | } 117 | 118 | /** 119 | * @return The node we go to when the feature is not active 120 | */ 121 | public DecisionTreeNode falseNode() 122 | { 123 | return falseNode; 124 | } 125 | 126 | //------------------------------------------------------------------------- 127 | 128 | } 129 | -------------------------------------------------------------------------------- /AI/src/search/mcts/playout/PlayoutStrategy.java: -------------------------------------------------------------------------------- 1 | package search.mcts.playout; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.json.JSONObject; 6 | 7 | import game.Game; 8 | import other.context.Context; 9 | import other.trial.Trial; 10 | import policies.GreedyPolicy; 11 | import policies.ProportionalPolicyClassificationTree; 12 | import policies.softmax.SoftmaxPolicyLinear; 13 | import policies.softmax.SoftmaxPolicyLogitTree; 14 | import search.mcts.MCTS; 15 | 16 | /** 17 | * Interface for Play-out strategies for MCTS 18 | * 19 | * @author Dennis Soemers 20 | */ 21 | public interface PlayoutStrategy 22 | { 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** 27 | * Runs full play-out 28 | * 29 | * @param mcts 30 | * @param context 31 | * @return Trial object at end of playout. 32 | */ 33 | public Trial runPlayout(final MCTS mcts, final Context context); 34 | 35 | /** 36 | * Allows a Playout strategy to tell Ludii whether or not it can support playing 37 | * any given game. 38 | * 39 | * @param game 40 | * @return False if the playout strategy cannot be used in a given game 41 | */ 42 | public boolean playoutSupportsGame(final Game game); 43 | 44 | /** 45 | * @return Flags indicating stats that should be backpropagated 46 | */ 47 | public int backpropFlags(); 48 | 49 | //------------------------------------------------------------------------- 50 | 51 | /** 52 | * Customise the play-out strategy based on a list of given string inputs. 53 | * 54 | * @param inputs 55 | */ 56 | public void customise(final String[] inputs); 57 | 58 | //------------------------------------------------------------------------- 59 | 60 | /** 61 | * @param json 62 | * @return Playout strategy constructed from given JSON object 63 | */ 64 | public static PlayoutStrategy fromJson(final JSONObject json) 65 | { 66 | PlayoutStrategy playout = null; 67 | final String strategy = json.getString("strategy"); 68 | 69 | if (strategy.equalsIgnoreCase("Random")) 70 | { 71 | return new RandomPlayout(200); 72 | } 73 | 74 | return playout; 75 | } 76 | 77 | //------------------------------------------------------------------------- 78 | 79 | /** 80 | * @param inputs 81 | * @return A play-out strategy constructed based on an array of inputs 82 | */ 83 | public static PlayoutStrategy constructPlayoutStrategy(final String[] inputs) 84 | { 85 | PlayoutStrategy playout = null; 86 | 87 | if (inputs[0].endsWith("random") || inputs[0].endsWith("randomplayout")) 88 | { 89 | playout = new RandomPlayout(); 90 | playout.customise(inputs); 91 | } 92 | else if (inputs[0].endsWith("mast")) 93 | { 94 | playout = new MAST(); 95 | playout.customise(inputs); 96 | } 97 | else if (inputs[0].endsWith("nst")) 98 | { 99 | playout = new NST(); 100 | playout.customise(inputs); 101 | } 102 | else if (inputs[0].endsWith("softmax") || inputs[0].endsWith("softmaxplayout") || inputs[0].endsWith("softmaxlinear")) 103 | { 104 | playout = new SoftmaxPolicyLinear(); 105 | playout.customise(inputs); 106 | } 107 | else if (inputs[0].endsWith("softmaxlogittree")) 108 | { 109 | playout = new SoftmaxPolicyLogitTree(); 110 | playout.customise(inputs); 111 | } 112 | else if (inputs[0].endsWith("classificationtreepolicy")) 113 | { 114 | playout = new ProportionalPolicyClassificationTree(); 115 | playout.customise(inputs); 116 | } 117 | else if (inputs[0].endsWith("greedy")) 118 | { 119 | playout = new GreedyPolicy(); 120 | playout.customise(inputs); 121 | } 122 | else 123 | { 124 | System.err.println("Unknown play-out strategy: " + Arrays.toString(inputs)); 125 | } 126 | 127 | return playout; 128 | } 129 | 130 | //------------------------------------------------------------------------- 131 | 132 | } 133 | -------------------------------------------------------------------------------- /Features/src/features/spatial/graph_search/GraphSearch.java: -------------------------------------------------------------------------------- 1 | package features.spatial.graph_search; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Queue; 7 | 8 | import features.spatial.Walk; 9 | import game.Game; 10 | import gnu.trove.list.array.TFloatArrayList; 11 | import gnu.trove.list.array.TIntArrayList; 12 | import gnu.trove.set.TIntSet; 13 | import gnu.trove.set.hash.TIntHashSet; 14 | import other.topology.TopologyElement; 15 | 16 | /** 17 | * Some useful graph search algorithms. These are intended to be used specifically 18 | * with features (e.g. for finding / constructing features), so they follow similar 19 | * rules as the Walks in features do (e.g. no diagonal connections, allow connections 20 | * to off-board "locations", ...) 21 | * 22 | * @author Dennis Soemers 23 | */ 24 | public class GraphSearch 25 | { 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | /** 30 | * Constructor 31 | */ 32 | private GraphSearch() 33 | { 34 | // no need to instantiate 35 | } 36 | 37 | //------------------------------------------------------------------------- 38 | 39 | /** 40 | * Implemented using uniform-cost search, we don't really have meaningful 41 | * costs or heuristics for Dijkstra's or A* 42 | * 43 | * @param game 44 | * @param startSite 45 | * @param destination 46 | * @return Shortest path from startSite to destination 47 | */ 48 | public static Path shortestPathTo(final Game game, final TopologyElement startSite, final TopologyElement destination) 49 | { 50 | final TIntSet alreadyVisited = new TIntHashSet(); 51 | 52 | final Queue fringe = new ArrayDeque(); 53 | List pathSites = new ArrayList(); 54 | pathSites.add(startSite); 55 | fringe.add(new Path(pathSites, new Walk())); 56 | alreadyVisited.add(startSite.index()); 57 | final List sites = game.graphPlayElements(); 58 | 59 | while (!fringe.isEmpty()) 60 | { 61 | final Path path = fringe.remove(); 62 | final TopologyElement pathEnd = path.destination(); 63 | final int numOrthos = pathEnd.sortedOrthos().length; 64 | final TFloatArrayList rotations = Walk.rotationsForNumOrthos(numOrthos); 65 | 66 | for (int i = 0; i < rotations.size(); ++i) 67 | { 68 | final float nextStep = rotations.getQuick(i); 69 | 70 | // create a new walk with this new step 71 | final Walk newWalk = new Walk(path.walk()); 72 | newWalk.steps().add(nextStep); 73 | 74 | // see where we would end up if we were to follow this walk 75 | final TIntArrayList destinations = newWalk.resolveWalk(game, startSite, 0.f, 1); 76 | 77 | if (destinations.size() != 1) 78 | { 79 | System.err.println("WARNING: GraphSearch.shortestPathTo() resolved " 80 | + "a walk with " + destinations.size() + " destinations!"); 81 | } 82 | 83 | final int endWalkIdx = destinations.getQuick(0); 84 | 85 | if (destination.index() == endWalkIdx) 86 | { 87 | // we're done 88 | pathSites = new ArrayList(path.sites); 89 | pathSites.add(destination); 90 | return new Path(pathSites, newWalk); 91 | } 92 | else if (endWalkIdx >= 0 && !alreadyVisited.contains(endWalkIdx)) 93 | { 94 | // new path for fringe 95 | alreadyVisited.add(endWalkIdx); 96 | pathSites = new ArrayList(path.sites); 97 | pathSites.add(sites.get(endWalkIdx)); 98 | fringe.add(new Path(pathSites, newWalk)); 99 | } 100 | } 101 | } 102 | 103 | //System.out.println("startVertex = " + startVertex); 104 | //System.out.println("destination = " + destination); 105 | 106 | return null; 107 | } 108 | 109 | //------------------------------------------------------------------------- 110 | 111 | } -------------------------------------------------------------------------------- /AI/src/optimisers/SGD.java: -------------------------------------------------------------------------------- 1 | package optimisers; 2 | 3 | import java.io.BufferedOutputStream; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.ObjectOutputStream; 7 | 8 | import main.collections.FVector; 9 | 10 | /** 11 | * A standard Stochastic Gradient Descent optimiser, with optional support 12 | * for a simple momentum term. 13 | * 14 | * @author Dennis Soemers 15 | */ 16 | public class SGD extends Optimiser 17 | { 18 | 19 | //------------------------------------------------------------------------- 20 | 21 | /** */ 22 | private static final long serialVersionUID = 1L; 23 | 24 | //------------------------------------------------------------------------- 25 | 26 | /** 27 | * Momentum term. 28 | * "Velocity" of previous update is scaled by this value and added to 29 | * subsequent update. 30 | */ 31 | protected final float momentum; 32 | 33 | /** 34 | * Last "velocity" vector. Used for momentum. 35 | */ 36 | private FVector lastVelocity = null; 37 | 38 | //------------------------------------------------------------------------- 39 | 40 | /** 41 | * Constructor 42 | * 43 | * @param baseStepSize 44 | */ 45 | public SGD(final float baseStepSize) 46 | { 47 | super(baseStepSize); 48 | this.momentum = 0.f; 49 | } 50 | 51 | /** 52 | * Constructor with momentum 53 | * 54 | * @param baseStepSize 55 | * @param momentum 56 | */ 57 | public SGD(final float baseStepSize, final float momentum) 58 | { 59 | super(baseStepSize); 60 | this.momentum = momentum; 61 | } 62 | 63 | //------------------------------------------------------------------------- 64 | 65 | @Override 66 | public void maximiseObjective( 67 | final FVector params, 68 | final FVector gradients) { 69 | 70 | final FVector velocity = gradients.copy(); 71 | velocity.mult(baseStepSize); 72 | 73 | if (momentum > 0.f && lastVelocity != null) 74 | { 75 | while (lastVelocity.dim() < velocity.dim()) 76 | { 77 | // feature set has grown, so also need to grow the lastVelocity 78 | // vector 79 | lastVelocity = lastVelocity.append(0.f); 80 | } 81 | 82 | velocity.addScaled(lastVelocity, momentum); 83 | } 84 | 85 | params.add(velocity); 86 | lastVelocity = velocity; 87 | } 88 | 89 | //------------------------------------------------------------------------- 90 | 91 | /** 92 | * @param lines 93 | * @return Constructs an SGD object from instructions in the 94 | * given array of lines 95 | */ 96 | public static SGD fromLines(final String[] lines) 97 | { 98 | float baseStepSize = 0.05f; 99 | float momentum = 0.f; 100 | 101 | for (String line : lines) 102 | { 103 | final String[] lineParts = line.split(","); 104 | 105 | //----------------------------------------------------------------- 106 | // main parts 107 | //----------------------------------------------------------------- 108 | if (lineParts[0].toLowerCase().startsWith("basestepsize=")) 109 | { 110 | baseStepSize = Float.parseFloat( 111 | lineParts[0].substring("basestepsize=".length())); 112 | } 113 | else if (lineParts[0].toLowerCase().startsWith("momentum=")) 114 | { 115 | momentum = Float.parseFloat( 116 | lineParts[0].substring("momentum=".length())); 117 | } 118 | } 119 | 120 | return new SGD(baseStepSize, momentum); 121 | } 122 | 123 | //------------------------------------------------------------------------- 124 | 125 | @Override 126 | public void writeToFile(final String filepath) 127 | { 128 | try 129 | ( 130 | final ObjectOutputStream out = 131 | new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(filepath))) 132 | ) 133 | { 134 | out.writeObject(this); 135 | out.flush(); 136 | out.close(); 137 | } 138 | catch (final IOException e) 139 | { 140 | e.printStackTrace(); 141 | } 142 | } 143 | 144 | //------------------------------------------------------------------------- 145 | 146 | } 147 | -------------------------------------------------------------------------------- /AI/src/search/mcts/finalmoveselection/RobustChild.java: -------------------------------------------------------------------------------- 1 | package search.mcts.finalmoveselection; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.ThreadLocalRandom; 6 | 7 | import main.collections.FVector; 8 | import other.move.Move; 9 | import other.state.State; 10 | import search.mcts.MCTS; 11 | import search.mcts.nodes.BaseNode; 12 | 13 | /** 14 | * Selects move corresponding to the most robust child (highest visit count), 15 | * with an additional tie-breaker based on value estimates. If the MCTS 16 | * has a learned selection policy, that can be used as a second tie-breaker. 17 | * 18 | * @author Dennis Soemers 19 | */ 20 | public final class RobustChild implements FinalMoveSelectionStrategy 21 | { 22 | 23 | //------------------------------------------------------------------------- 24 | 25 | @Override 26 | public Move selectMove(final MCTS mcts, final BaseNode rootNode) 27 | { 28 | final List bestActions = new ArrayList(); 29 | double bestActionValueEstimate = Double.NEGATIVE_INFINITY; 30 | float bestActionPolicyPrior = Float.NEGATIVE_INFINITY; 31 | final State rootState = rootNode.contextRef().state(); 32 | final int moverAgent = rootState.playerToAgent(rootState.mover()); 33 | int maxNumVisits = -1; 34 | 35 | final FVector priorPolicy; 36 | if (mcts.learnedSelectionPolicy() == null) 37 | priorPolicy = null; 38 | else 39 | priorPolicy = rootNode.learnedSelectionPolicy(); 40 | 41 | final int numChildren = rootNode.numLegalMoves(); 42 | for (int i = 0; i < numChildren; ++i) 43 | { 44 | final BaseNode child = rootNode.childForNthLegalMove(i); 45 | final int numVisits = child == null ? 0 : child.numVisits(); 46 | final double childValueEstimate = child == null ? 0.0 : child.expectedScore(moverAgent); 47 | final float childPriorPolicy = priorPolicy == null ? -1.f : priorPolicy.get(i); 48 | 49 | if (numVisits > maxNumVisits) 50 | { 51 | maxNumVisits = numVisits; 52 | bestActions.clear(); 53 | bestActionValueEstimate = childValueEstimate; 54 | bestActionPolicyPrior = childPriorPolicy; 55 | bestActions.add(rootNode.nthLegalMove(i)); 56 | } 57 | else if (numVisits == maxNumVisits) 58 | { 59 | if (childValueEstimate > bestActionValueEstimate) 60 | { 61 | // Tie-breaker; prefer higher value estimates 62 | bestActions.clear(); 63 | bestActionValueEstimate = childValueEstimate; 64 | bestActionPolicyPrior = childPriorPolicy; 65 | bestActions.add(rootNode.nthLegalMove(i)); 66 | } 67 | else if (childValueEstimate == bestActionValueEstimate) 68 | { 69 | // Tie for both num visits and also for estimated value; prefer higher prior policy 70 | if (childPriorPolicy > bestActionPolicyPrior) 71 | { 72 | bestActions.clear(); 73 | bestActionValueEstimate = childValueEstimate; 74 | bestActionPolicyPrior = childPriorPolicy; 75 | bestActions.add(rootNode.nthLegalMove(i)); 76 | } 77 | else if (childPriorPolicy == bestActionPolicyPrior) 78 | { 79 | // Tie for everything 80 | bestActions.add(rootNode.nthLegalMove(i)); 81 | } 82 | } 83 | } 84 | } 85 | 86 | return bestActions.get(ThreadLocalRandom.current().nextInt(bestActions.size())); 87 | } 88 | 89 | //------------------------------------------------------------------------- 90 | 91 | @Override 92 | public void customise(final String[] inputs) 93 | { 94 | // Do nothing 95 | } 96 | 97 | //------------------------------------------------------------------------- 98 | 99 | } 100 | -------------------------------------------------------------------------------- /Features/src/features/spatial/instances/OneOfMustWho.java: -------------------------------------------------------------------------------- 1 | package features.spatial.instances; 2 | 3 | import game.types.board.SiteType; 4 | import main.collections.ChunkSet; 5 | import other.state.State; 6 | import other.state.container.ContainerState; 7 | 8 | /** 9 | * Simultaneously tests multiple chunks of the "who" ChunkSet. 10 | * 11 | * TODO could make special cases of this class for cells, vertices, and edges 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public final class OneOfMustWho implements BitwiseTest 16 | { 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | /** 21 | * Set of chunks of which at least one must match game state's Who 22 | * ChunkSet for test to succeed. 23 | */ 24 | protected final ChunkSet mustWhos; 25 | 26 | /** Mask for must-who tests */ 27 | protected final ChunkSet mustWhosMask; 28 | 29 | /** The first non-zero word in the mustWhosMask ChunkSet */ 30 | protected final int firstUsedWord; 31 | 32 | /** Graph element type we want to test on */ 33 | protected final SiteType graphElementType; 34 | 35 | //------------------------------------------------------------------------- 36 | 37 | /** 38 | * Constructor 39 | * @param mustWhos 40 | * @param mustWhosMask 41 | * @param graphElementType 42 | */ 43 | public OneOfMustWho(final ChunkSet mustWhos, final ChunkSet mustWhosMask, final SiteType graphElementType) 44 | { 45 | this.mustWhos = mustWhos; 46 | this.mustWhosMask = mustWhosMask; 47 | this.graphElementType = graphElementType; 48 | firstUsedWord = mustWhosMask.nextSetBit(0) / Long.SIZE; 49 | } 50 | 51 | //------------------------------------------------------------------------- 52 | 53 | @Override 54 | public final boolean matches(final State state) 55 | { 56 | final ContainerState container = state.containerStates()[0]; 57 | switch (graphElementType) 58 | { 59 | case Cell: 60 | return (container.violatesNotWhoCell(mustWhosMask, mustWhos, firstUsedWord)); 61 | case Vertex: 62 | return (container.violatesNotWhoVertex(mustWhosMask, mustWhos, firstUsedWord)); 63 | case Edge: 64 | return (container.violatesNotWhoEdge(mustWhosMask, mustWhos, firstUsedWord)); 65 | //$CASES-OMITTED$ Hint 66 | default: 67 | break; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | //------------------------------------------------------------------------- 74 | 75 | @Override 76 | public final boolean hasNoTests() 77 | { 78 | return false; 79 | } 80 | 81 | @Override 82 | public final boolean onlyRequiresSingleMustEmpty() 83 | { 84 | return false; 85 | } 86 | 87 | @Override 88 | public final boolean onlyRequiresSingleMustWho() 89 | { 90 | return true; 91 | } 92 | 93 | @Override 94 | public final boolean onlyRequiresSingleMustWhat() 95 | { 96 | return false; 97 | } 98 | 99 | @Override 100 | public final SiteType graphElementType() 101 | { 102 | return graphElementType; 103 | } 104 | 105 | //------------------------------------------------------------------------- 106 | 107 | /** 108 | * @return mustWhos ChunkSet 109 | */ 110 | public final ChunkSet mustWhos() 111 | { 112 | return mustWhos; 113 | } 114 | 115 | /** 116 | * @return mustWhosMask ChunkSet 117 | */ 118 | public final ChunkSet mustWhosMask() 119 | { 120 | return mustWhosMask; 121 | } 122 | 123 | //------------------------------------------------------------------------- 124 | 125 | @Override 126 | public String toString() 127 | { 128 | String requirementsStr = ""; 129 | 130 | for (int i = 0; i < mustWhos.numChunks(); ++i) 131 | { 132 | if (mustWhosMask.getChunk(i) != 0) 133 | { 134 | requirementsStr += 135 | i + " must belong to " + mustWhos.getChunk(i) + ", "; 136 | } 137 | } 138 | 139 | return String.format( 140 | "One of these who-conditions must hold: [%s]", 141 | requirementsStr); 142 | } 143 | 144 | //------------------------------------------------------------------------- 145 | 146 | } 147 | -------------------------------------------------------------------------------- /Features/src/features/spatial/instances/OneOfMustWhat.java: -------------------------------------------------------------------------------- 1 | package features.spatial.instances; 2 | 3 | import game.types.board.SiteType; 4 | import main.collections.ChunkSet; 5 | import other.state.State; 6 | import other.state.container.ContainerState; 7 | 8 | /** 9 | * Simultaneously tests multiple chunks of the "what" ChunkSet. 10 | * 11 | * TODO could make special cases of this class for cells, vertices, and edges 12 | * 13 | * @author Dennis Soemers 14 | */ 15 | public final class OneOfMustWhat implements BitwiseTest 16 | { 17 | 18 | //------------------------------------------------------------------------- 19 | 20 | /** 21 | * Set of chunks of which at least one must match game state's What 22 | * ChunkSet for test to succeed. 23 | */ 24 | protected final ChunkSet mustWhats; 25 | 26 | /** Mask for must-what tests */ 27 | protected final ChunkSet mustWhatsMask; 28 | 29 | /** The first non-zero word in the mustWhatsMask ChunkSet */ 30 | protected final int firstUsedWord; 31 | 32 | /** Graph element type we want to test on */ 33 | protected final SiteType graphElementType; 34 | 35 | //------------------------------------------------------------------------- 36 | 37 | /** 38 | * Constructor 39 | * @param mustWhats 40 | * @param mustWhatsMask 41 | * @param graphElementType 42 | */ 43 | public OneOfMustWhat(final ChunkSet mustWhats, final ChunkSet mustWhatsMask, final SiteType graphElementType) 44 | { 45 | this.mustWhats = mustWhats; 46 | this.mustWhatsMask = mustWhatsMask; 47 | this.graphElementType = graphElementType; 48 | firstUsedWord = mustWhatsMask.nextSetBit(0) / Long.SIZE; 49 | } 50 | 51 | //------------------------------------------------------------------------- 52 | 53 | @Override 54 | public final boolean matches(final State state) 55 | { 56 | final ContainerState container = state.containerStates()[0]; 57 | switch (graphElementType) 58 | { 59 | case Cell: 60 | return (container.violatesNotWhatCell(mustWhatsMask, mustWhats, firstUsedWord)); 61 | case Vertex: 62 | return (container.violatesNotWhatVertex(mustWhatsMask, mustWhats, firstUsedWord)); 63 | case Edge: 64 | return (container.violatesNotWhatEdge(mustWhatsMask, mustWhats, firstUsedWord)); 65 | //$CASES-OMITTED$ Hint 66 | default: 67 | break; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | //------------------------------------------------------------------------- 74 | 75 | @Override 76 | public final boolean hasNoTests() 77 | { 78 | return false; 79 | } 80 | 81 | @Override 82 | public final boolean onlyRequiresSingleMustEmpty() 83 | { 84 | return false; 85 | } 86 | 87 | @Override 88 | public final boolean onlyRequiresSingleMustWho() 89 | { 90 | return true; 91 | } 92 | 93 | @Override 94 | public final boolean onlyRequiresSingleMustWhat() 95 | { 96 | return false; 97 | } 98 | 99 | @Override 100 | public final SiteType graphElementType() 101 | { 102 | return graphElementType; 103 | } 104 | 105 | //------------------------------------------------------------------------- 106 | 107 | /** 108 | * @return mustWhats ChunkSet 109 | */ 110 | public final ChunkSet mustWhats() 111 | { 112 | return mustWhats; 113 | } 114 | 115 | /** 116 | * @return mustWhatsMask ChunkSet 117 | */ 118 | public final ChunkSet mustWhatsMask() 119 | { 120 | return mustWhatsMask; 121 | } 122 | 123 | //------------------------------------------------------------------------- 124 | 125 | @Override 126 | public String toString() 127 | { 128 | String requirementsStr = ""; 129 | 130 | for (int i = 0; i < mustWhats.numChunks(); ++i) 131 | { 132 | if (mustWhatsMask.getChunk(i) != 0) 133 | { 134 | requirementsStr += 135 | i + " must contain " + mustWhats.getChunk(i) + ", "; 136 | } 137 | } 138 | 139 | return String.format( 140 | "One of these what-conditions must hold: [%s]", 141 | requirementsStr); 142 | } 143 | 144 | //------------------------------------------------------------------------- 145 | 146 | } 147 | -------------------------------------------------------------------------------- /AI/src/search/mcts/selection/UCB1.java: -------------------------------------------------------------------------------- 1 | package search.mcts.selection; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import other.state.State; 6 | import search.mcts.MCTS; 7 | import search.mcts.nodes.BaseNode; 8 | 9 | /** 10 | * UCB1 Selection Strategy, as commonly used in UCT. 11 | * 12 | * @author Dennis Soemers 13 | */ 14 | public final class UCB1 implements SelectionStrategy 15 | { 16 | 17 | //------------------------------------------------------------------------- 18 | 19 | /** Exploration constant */ 20 | protected double explorationConstant; 21 | 22 | //------------------------------------------------------------------------- 23 | 24 | /** 25 | * Constructor with default value sqrt(2.0) for exploration constant 26 | */ 27 | public UCB1() 28 | { 29 | this(Math.sqrt(2.0)); 30 | } 31 | 32 | /** 33 | * Constructor with parameter for exploration constant 34 | * @param explorationConstant 35 | */ 36 | public UCB1(final double explorationConstant) 37 | { 38 | this.explorationConstant = explorationConstant; 39 | } 40 | 41 | //------------------------------------------------------------------------- 42 | 43 | @Override 44 | public int select(final MCTS mcts, final BaseNode current) 45 | { 46 | int bestIdx = -1; 47 | double bestValue = Double.NEGATIVE_INFINITY; 48 | int numBestFound = 0; 49 | 50 | final double parentLog = Math.log(Math.max(1, current.sumLegalChildVisits())); 51 | final int numChildren = current.numLegalMoves(); 52 | final State state = current.contextRef().state(); 53 | final int moverAgent = state.playerToAgent(state.mover()); 54 | final double unvisitedValueEstimate = current.valueEstimateUnvisitedChildren(moverAgent); 55 | 56 | for (int i = 0; i < numChildren; ++i) 57 | { 58 | final BaseNode child = current.childForNthLegalMove(i); 59 | final double exploit; 60 | final double explore; 61 | 62 | if (child == null) 63 | { 64 | exploit = unvisitedValueEstimate; 65 | explore = Math.sqrt(parentLog); 66 | } 67 | else 68 | { 69 | exploit = child.exploitationScore(moverAgent); 70 | final int numVisits = child.numVisits() + child.numVirtualVisits(); 71 | explore = Math.sqrt(parentLog / numVisits); 72 | } 73 | 74 | final double ucb1Value = exploit + explorationConstant * explore; 75 | //System.out.println("ucb1Value = " + ucb1Value); 76 | //System.out.println("exploit = " + exploit); 77 | //System.out.println("explore = " + explore); 78 | 79 | if (ucb1Value > bestValue) 80 | { 81 | bestValue = ucb1Value; 82 | bestIdx = i; 83 | numBestFound = 1; 84 | } 85 | else if 86 | ( 87 | ucb1Value == bestValue 88 | && 89 | ThreadLocalRandom.current().nextInt() % ++numBestFound == 0 90 | ) 91 | { 92 | bestIdx = i; 93 | } 94 | } 95 | 96 | return bestIdx; 97 | } 98 | 99 | //------------------------------------------------------------------------- 100 | 101 | @Override 102 | public int backpropFlags() 103 | { 104 | return 0; 105 | } 106 | 107 | @Override 108 | public int expansionFlags() 109 | { 110 | return 0; 111 | } 112 | 113 | @Override 114 | public void customise(final String[] inputs) 115 | { 116 | if (inputs.length > 1) 117 | { 118 | // We have more inputs than just the name of the strategy 119 | for (int i = 1; i < inputs.length; ++i) 120 | { 121 | final String input = inputs[i]; 122 | 123 | if (input.startsWith("explorationconstant=")) 124 | { 125 | explorationConstant = Double.parseDouble( 126 | input.substring("explorationconstant=".length())); 127 | } 128 | else 129 | { 130 | System.err.println("UCB1 ignores unknown customisation: " + input); 131 | } 132 | } 133 | } 134 | } 135 | 136 | //------------------------------------------------------------------------- 137 | 138 | } 139 | -------------------------------------------------------------------------------- /AI/src/search/mcts/selection/AG0Selection.java: -------------------------------------------------------------------------------- 1 | package search.mcts.selection; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import main.collections.FVector; 6 | import other.state.State; 7 | import search.mcts.MCTS; 8 | import search.mcts.nodes.BaseNode; 9 | 10 | /** 11 | * Selection strategy used by AlphaGo Zero (described there as a variant of 12 | * PUCB1 proposed by Rosin (2011), but it's a variant, not exactly the same). 13 | * 14 | * @author Dennis Soemers 15 | * 16 | */ 17 | public final class AG0Selection implements SelectionStrategy 18 | { 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** Exploration constant for AlphaGo Zero's selection strategy */ 23 | protected double explorationConstant; 24 | 25 | //------------------------------------------------------------------------- 26 | 27 | /** 28 | * Constructor with default exploration constant of 2.5. 29 | */ 30 | public AG0Selection() 31 | { 32 | this(2.5); 33 | } 34 | 35 | /** 36 | * Constructor with custom exploration constant 37 | * @param explorationConstant 38 | */ 39 | public AG0Selection(final double explorationConstant) 40 | { 41 | this.explorationConstant = explorationConstant; 42 | } 43 | 44 | //------------------------------------------------------------------------- 45 | 46 | @Override 47 | public int select(final MCTS mcts, final BaseNode current) 48 | { 49 | int bestIdx = -1; 50 | double bestValue = Double.NEGATIVE_INFINITY; 51 | int numBestFound = 0; 52 | 53 | final FVector distribution = current.learnedSelectionPolicy(); 54 | final double parentSqrt = Math.sqrt(current.sumLegalChildVisits()); 55 | 56 | final int numChildren = current.numLegalMoves(); 57 | final State state = current.contextRef().state(); 58 | final int moverAgent = state.playerToAgent(state.mover()); 59 | final double unvisitedValueEstimate = 60 | current.valueEstimateUnvisitedChildren(moverAgent); 61 | 62 | for (int i = 0; i < numChildren; ++i) 63 | { 64 | final BaseNode child = current.childForNthLegalMove(i); 65 | final double exploit; 66 | final int numVisits; 67 | 68 | if (child == null) 69 | { 70 | exploit = unvisitedValueEstimate; 71 | numVisits = 0; 72 | } 73 | else 74 | { 75 | exploit = child.exploitationScore(moverAgent); 76 | numVisits = child.numVisits() + child.numVirtualVisits(); 77 | } 78 | 79 | final float priorProb = distribution.get(i); 80 | final double explore = (parentSqrt == 0.0) ? 1.0 : parentSqrt / (1.0 + numVisits); 81 | 82 | final double pucb1Value = exploit + explorationConstant * priorProb * explore; 83 | 84 | if (pucb1Value > bestValue) 85 | { 86 | bestValue = pucb1Value; 87 | bestIdx = i; 88 | numBestFound = 1; 89 | } 90 | else if (pucb1Value == bestValue && ThreadLocalRandom.current().nextInt() % ++numBestFound == 0) 91 | { 92 | bestIdx = i; 93 | } 94 | } 95 | 96 | return bestIdx; 97 | } 98 | 99 | //------------------------------------------------------------------------- 100 | 101 | @Override 102 | public int backpropFlags() 103 | { 104 | return 0; 105 | } 106 | 107 | @Override 108 | public int expansionFlags() 109 | { 110 | return 0; 111 | } 112 | 113 | @Override 114 | public void customise(final String[] inputs) 115 | { 116 | if (inputs.length > 1) 117 | { 118 | // we have more inputs than just the name of the strategy 119 | for (int i = 1; i < inputs.length; ++i) 120 | { 121 | final String input = inputs[i]; 122 | 123 | if (input.startsWith("explorationconstant=")) 124 | { 125 | explorationConstant = Double.parseDouble(input.substring("explorationconstant=".length())); 126 | } 127 | else 128 | { 129 | System.err.println("AG0Selection ignores unknown customisation: " + input); 130 | } 131 | } 132 | } 133 | } 134 | 135 | //------------------------------------------------------------------------- 136 | 137 | } 138 | -------------------------------------------------------------------------------- /AI/src/search/minimax/NaiveActionBasedSelection.java: -------------------------------------------------------------------------------- 1 | package search.minimax; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.concurrent.ThreadLocalRandom; 7 | 8 | import game.Game; 9 | import main.collections.FastArrayList; 10 | import other.AI; 11 | import other.context.Context; 12 | import other.move.Move; 13 | import policies.softmax.SoftmaxFromMetadataSelection; 14 | import policies.softmax.SoftmaxPolicy; 15 | import utils.data_structures.ScoredMove; 16 | 17 | /** 18 | * Naive AI that just picks the most promising action according to its learned selection policy 19 | * based on actions, with no exploration. 20 | * 21 | * @author cyprien 22 | */ 23 | 24 | public class NaiveActionBasedSelection extends AI 25 | { 26 | /** A learned policy to use in Selection phase */ 27 | protected SoftmaxPolicy learnedSelectionPolicy = null; 28 | 29 | /** Current list of moves available in root */ 30 | protected FastArrayList currentRootMoves = null; 31 | 32 | protected float selectionEpsilon = 0f; 33 | 34 | protected int selectActionNbCalls = 0; 35 | 36 | //------------------------------------------------------------------------- 37 | 38 | /** 39 | * Constructor: 40 | */ 41 | public NaiveActionBasedSelection () 42 | { 43 | super(); 44 | friendlyName = "Naive Action Based Selection"; 45 | } 46 | 47 | //------------------------------------------------------------------------- 48 | 49 | @Override 50 | public Move selectAction 51 | ( 52 | final Game game, 53 | final Context context, 54 | final double maxSeconds, 55 | final int maxIterations, 56 | final int maxDepth 57 | ) 58 | { 59 | currentRootMoves = new FastArrayList(game.moves(context).moves()); 60 | 61 | Move selectedMove; 62 | if ((learnedSelectionPolicy != null) && (ThreadLocalRandom.current().nextDouble(1.) < selectionEpsilon)) 63 | { 64 | final int numRootMoves = currentRootMoves.size(); 65 | 66 | final List consideredMoveIndices = new ArrayList(numRootMoves); 67 | 68 | for (int i=0; i bestValue) 88 | { 89 | bestValue = ucb1Value; 90 | bestIdx = i; 91 | numBestFound = 1; 92 | } 93 | else if 94 | ( 95 | ucb1Value == bestValue 96 | && 97 | ThreadLocalRandom.current().nextInt() % ++numBestFound == 0 98 | ) 99 | { 100 | bestIdx = i; 101 | } 102 | } 103 | 104 | return bestIdx; 105 | } 106 | 107 | //------------------------------------------------------------------------- 108 | 109 | @Override 110 | public int backpropFlags() 111 | { 112 | return 0; 113 | } 114 | 115 | @Override 116 | public int expansionFlags() 117 | { 118 | return MCTS.HEURISTIC_INIT; 119 | } 120 | 121 | @Override 122 | public void customise(final String[] inputs) 123 | { 124 | if (inputs.length > 1) 125 | { 126 | // we have more inputs than just the name of the strategy 127 | for (int i = 1; i < inputs.length; ++i) 128 | { 129 | final String input = inputs[i]; 130 | 131 | if (input.startsWith("explorationconstant=")) 132 | { 133 | explorationConstant = Double.parseDouble( 134 | input.substring("explorationconstant=".length())); 135 | } 136 | else 137 | { 138 | System.err.println("Progressive Bias ignores unknown customisation: " + input); 139 | } 140 | } 141 | } 142 | } 143 | 144 | //------------------------------------------------------------------------- 145 | 146 | } 147 | -------------------------------------------------------------------------------- /AI/src/search/mcts/selection/McBRAVE.java: -------------------------------------------------------------------------------- 1 | package search.mcts.selection; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import other.move.Move; 6 | import other.state.State; 7 | import search.mcts.MCTS; 8 | import search.mcts.MCTS.MoveKey; 9 | import search.mcts.backpropagation.BackpropagationStrategy; 10 | import search.mcts.nodes.BaseNode; 11 | import search.mcts.nodes.BaseNode.NodeStatistics; 12 | 13 | /** 14 | * TODO 15 | * 16 | * @author Dennis Soemers 17 | */ 18 | public class McBRAVE implements SelectionStrategy 19 | { 20 | 21 | //------------------------------------------------------------------------- 22 | 23 | /** Hyperparameter used in computation of weight for AMAF term */ 24 | protected final double bias; 25 | 26 | //------------------------------------------------------------------------- 27 | 28 | /** 29 | * Constructor with default value of bias = 10^(-6), 30 | * loosely based on hyperparameter tuning in GRAVE paper (though 31 | * that paper found many different optimal values for different games). 32 | */ 33 | public McBRAVE() 34 | { 35 | this.bias = 10.0e-6; 36 | } 37 | 38 | /** 39 | * Constructor 40 | * @param bias 41 | */ 42 | public McBRAVE(final double bias) 43 | { 44 | this.bias = bias; 45 | } 46 | 47 | //------------------------------------------------------------------------- 48 | 49 | @Override 50 | public int select(final MCTS mcts, final BaseNode current) 51 | { 52 | int bestIdx = -1; 53 | double bestValue = Double.NEGATIVE_INFINITY; 54 | int numBestFound = 0; 55 | 56 | final int numChildren = current.numLegalMoves(); 57 | final State state = current.contextRef().state(); 58 | final int moverAgent = state.playerToAgent(state.mover()); 59 | final double unvisitedValueEstimate = current.valueEstimateUnvisitedChildren(moverAgent); 60 | 61 | for (int i = 0; i < numChildren; ++i) 62 | { 63 | final BaseNode child = current.childForNthLegalMove(i); 64 | final double meanScore; 65 | final double meanAMAF; 66 | final double beta; 67 | 68 | if (child == null) 69 | { 70 | meanScore = unvisitedValueEstimate; 71 | meanAMAF = 0.0; 72 | beta = 0.0; 73 | } 74 | else 75 | { 76 | meanScore = child.exploitationScore(moverAgent); 77 | final Move move = child.parentMove(); 78 | 79 | int accumVisits = 0; 80 | double accumScore = 0.0; 81 | final MoveKey moveKey = new MoveKey(move, current.contextRef().trial().numMoves()); 82 | 83 | BaseNode raveNode = current; 84 | while (raveNode != null) 85 | { 86 | final NodeStatistics graveStats = raveNode.graveStats(moveKey); 87 | 88 | if (graveStats != null) 89 | { 90 | accumScore += graveStats.accumulatedScore; 91 | accumVisits += graveStats.visitCount; 92 | } 93 | 94 | raveNode = raveNode.parent(); 95 | } 96 | 97 | if (accumVisits == 0) 98 | { 99 | meanAMAF = 0.0; 100 | beta = 0.0; 101 | } 102 | else 103 | { 104 | final int childVisits = child.numVisits() + child.numVirtualVisits(); 105 | meanAMAF = accumScore / accumVisits; 106 | beta = accumVisits / (accumVisits + childVisits + bias * accumVisits * childVisits); 107 | } 108 | } 109 | 110 | final double graveValue = (1.0 - beta) * meanScore + beta * meanAMAF; 111 | 112 | if (graveValue > bestValue) 113 | { 114 | bestValue = graveValue; 115 | bestIdx = i; 116 | numBestFound = 1; 117 | } 118 | else if 119 | ( 120 | graveValue == bestValue 121 | && 122 | ThreadLocalRandom.current().nextInt() % ++numBestFound == 0 123 | ) 124 | { 125 | bestIdx = i; 126 | } 127 | } 128 | 129 | return bestIdx; 130 | } 131 | 132 | //------------------------------------------------------------------------- 133 | 134 | @Override 135 | public int backpropFlags() 136 | { 137 | return BackpropagationStrategy.GRAVE_STATS; 138 | } 139 | 140 | @Override 141 | public int expansionFlags() 142 | { 143 | return 0; 144 | } 145 | 146 | @Override 147 | public void customise(final String[] inputs) 148 | { 149 | // TODO 150 | } 151 | 152 | //------------------------------------------------------------------------- 153 | 154 | } 155 | -------------------------------------------------------------------------------- /AI/src/search/mcts/selection/UCB1Tuned.java: -------------------------------------------------------------------------------- 1 | package search.mcts.selection; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import other.state.State; 6 | import search.mcts.MCTS; 7 | import search.mcts.nodes.BaseNode; 8 | 9 | /** 10 | * UCB1-Tuned Selection strategy. The original paper by Auer et al. used 1/4 as the 11 | * upper bound on the variance of a Bernoulli random variable. We expect values 12 | * in the [-1, 1] range, rather than the [0, 1] range in our MCTS, so 13 | * we use 1 as an upper bound on the variance of this random variable. 14 | * 15 | * @author Dennis Soemers 16 | */ 17 | public final class UCB1Tuned implements SelectionStrategy 18 | { 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** Upper bound on variance of random variable in [-1, 1] range */ 23 | protected static final double VARIANCE_UPPER_BOUND = 1.0; 24 | 25 | /** Exploration constant */ 26 | protected double explorationConstant; 27 | 28 | //------------------------------------------------------------------------- 29 | 30 | /** 31 | * Constructor with default value sqrt(2.0) for exploration constant 32 | */ 33 | public UCB1Tuned() 34 | { 35 | this(Math.sqrt(2.0)); 36 | } 37 | 38 | /** 39 | * Constructor with parameter for exploration constant 40 | * @param explorationConstant 41 | */ 42 | public UCB1Tuned(final double explorationConstant) 43 | { 44 | this.explorationConstant = explorationConstant; 45 | } 46 | 47 | //------------------------------------------------------------------------- 48 | 49 | @Override 50 | public int select(final MCTS mcts, final BaseNode current) 51 | { 52 | int bestIdx = -1; 53 | double bestValue = Double.NEGATIVE_INFINITY; 54 | int numBestFound = 0; 55 | 56 | final double parentLog = Math.log(Math.max(1, current.sumLegalChildVisits())); 57 | final int numChildren = current.numLegalMoves(); 58 | final State state = current.contextRef().state(); 59 | final int moverAgent = state.playerToAgent(state.mover()); 60 | final double unvisitedValueEstimate = current.valueEstimateUnvisitedChildren(moverAgent); 61 | 62 | for (int i = 0; i < numChildren; ++i) 63 | { 64 | final BaseNode child = current.childForNthLegalMove(i); 65 | final double exploit; 66 | final double sampleVariance; 67 | final double visitsFraction; 68 | 69 | if (child == null) 70 | { 71 | exploit = unvisitedValueEstimate; 72 | sampleVariance = VARIANCE_UPPER_BOUND; 73 | visitsFraction = parentLog; 74 | } 75 | else 76 | { 77 | exploit = child.exploitationScore(moverAgent); 78 | final int numChildVisits = child.numVisits() + child.numVirtualVisits(); 79 | sampleVariance = Math.max(child.sumSquaredScores(moverAgent) / numChildVisits - exploit*exploit, 0.0); 80 | visitsFraction = parentLog / numChildVisits; 81 | } 82 | 83 | final double ucb1TunedValue = exploit + 84 | Math.sqrt 85 | ( 86 | visitsFraction * Math.min(VARIANCE_UPPER_BOUND, sampleVariance + explorationConstant * Math.sqrt(visitsFraction)) 87 | ); 88 | 89 | if (ucb1TunedValue > bestValue) 90 | { 91 | bestValue = ucb1TunedValue; 92 | bestIdx = i; 93 | numBestFound = 1; 94 | } 95 | else if 96 | ( 97 | ucb1TunedValue == bestValue 98 | && 99 | ThreadLocalRandom.current().nextInt() % ++numBestFound == 0 100 | ) 101 | { 102 | bestIdx = i; 103 | } 104 | } 105 | 106 | return bestIdx; 107 | } 108 | 109 | //------------------------------------------------------------------------- 110 | 111 | @Override 112 | public int backpropFlags() 113 | { 114 | return 0; 115 | } 116 | 117 | @Override 118 | public int expansionFlags() 119 | { 120 | return 0; 121 | } 122 | 123 | @Override 124 | public void customise(final String[] inputs) 125 | { 126 | if (inputs.length > 1) 127 | { 128 | // We have more inputs than just the name of the strategy 129 | for (int i = 1; i < inputs.length; ++i) 130 | { 131 | final String input = inputs[i]; 132 | 133 | if (input.startsWith("explorationconstant=")) 134 | { 135 | explorationConstant = Double.parseDouble(input.substring("explorationconstant=".length())); 136 | } 137 | else 138 | { 139 | System.err.println("UCB1Tuned ignores unknown customisation: " + input); 140 | } 141 | } 142 | } 143 | } 144 | 145 | //------------------------------------------------------------------------- 146 | 147 | } 148 | -------------------------------------------------------------------------------- /AI/src/utils/data_structures/ludeme_trees/LudemeTreeUtils.java: -------------------------------------------------------------------------------- 1 | package utils.data_structures.ludeme_trees; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Modifier; 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import main.ReflectionUtils; 12 | import other.Ludeme; 13 | import utils.data_structures.support.zhang_shasha.Node; 14 | import utils.data_structures.support.zhang_shasha.Tree; 15 | 16 | /** 17 | * Utils for building trees of Ludemes for AI purposes. 18 | * 19 | * @author Dennis Soemers 20 | */ 21 | public class LudemeTreeUtils 22 | { 23 | 24 | /** 25 | * Builds a Zhang-Shasha tree (for tree edit distance computations) for the given 26 | * root ludeme. 27 | * 28 | * @param rootLudeme 29 | * @return Tree that can be used for Zhang-Shasha tree edit distance computations 30 | */ 31 | public static Tree buildLudemeZhangShashaTree(final Ludeme rootLudeme) 32 | { 33 | if (rootLudeme == null) 34 | { 35 | return new Tree(new Node("")); 36 | } 37 | else 38 | { 39 | final Node root = buildTree(rootLudeme, new HashMap>()); 40 | return new Tree(root); 41 | } 42 | } 43 | 44 | /** 45 | * Recursively builds tree for given ludeme 46 | * @param ludeme Root of ludemes-subtree to traverse 47 | * @param visited Map of fields we've already visited, to avoid cycles 48 | * @return Root node for the subtree rooted in given ludeme 49 | */ 50 | private static Node buildTree 51 | ( 52 | final Ludeme ludeme, 53 | final Map> visited 54 | ) 55 | { 56 | final Class clazz = ludeme.getClass(); 57 | final List fields = ReflectionUtils.getAllFields(clazz); 58 | final Node node = new Node(clazz.getName()); 59 | 60 | try 61 | { 62 | for (final Field field : fields) 63 | { 64 | if (field.getName().contains("$")) 65 | continue; 66 | 67 | field.setAccessible(true); 68 | 69 | if ((field.getModifiers() & Modifier.STATIC) != 0) 70 | continue; 71 | 72 | if (visited.containsKey(ludeme) && visited.get(ludeme).contains(field.getName())) 73 | continue; // avoid stack overflow 74 | 75 | final Object value = field.get(ludeme); 76 | 77 | if (!visited.containsKey(ludeme)) 78 | visited.put(ludeme, new HashSet()); 79 | 80 | visited.get(ludeme).add(field.getName()); 81 | 82 | if (value != null) 83 | { 84 | final Class valueClass = value.getClass(); 85 | 86 | if (Enum.class.isAssignableFrom(valueClass)) 87 | continue; 88 | 89 | if (Ludeme.class.isAssignableFrom(valueClass)) 90 | { 91 | final Ludeme innerLudeme = (Ludeme) value; 92 | final Node child = buildTree(innerLudeme, visited); 93 | node.children.add(child); 94 | } 95 | else if (valueClass.isArray()) 96 | { 97 | final Object[] array = ReflectionUtils.castArray(value); 98 | 99 | for (final Object element : array) 100 | { 101 | if (element != null) 102 | { 103 | final Class elementClass = element.getClass(); 104 | 105 | if (Ludeme.class.isAssignableFrom(elementClass)) 106 | { 107 | final Ludeme innerLudeme = (Ludeme) element; 108 | final Node child = buildTree(innerLudeme, visited); 109 | node.children.add(child); 110 | } 111 | } 112 | } 113 | } 114 | else if (Iterable.class.isAssignableFrom(valueClass)) 115 | { 116 | final Iterable iterable = (Iterable) value; 117 | 118 | for (final Object element : iterable) 119 | { 120 | if (element != null) 121 | { 122 | final Class elementClass = element.getClass(); 123 | 124 | if (Ludeme.class.isAssignableFrom(elementClass)) 125 | { 126 | final Ludeme innerLudeme = (Ludeme) element; 127 | final Node child = buildTree(innerLudeme, visited); 128 | node.children.add(child); 129 | } 130 | } 131 | } 132 | } 133 | else if (field.getType().isPrimitive() || String.class.isAssignableFrom(valueClass)) 134 | { 135 | node.children.add(new Node(value.toString())); 136 | } 137 | } 138 | else 139 | { 140 | node.children.add(new Node("null")); 141 | } 142 | 143 | // Remove again, to avoid excessively shortening the subtree if we encounter 144 | // this object again later 145 | visited.get(ludeme).remove(field.getName()); 146 | } 147 | } 148 | catch (final IllegalArgumentException | IllegalAccessException e) 149 | { 150 | e.printStackTrace(); 151 | } 152 | 153 | return node; 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /AI/src/utils/analysis/BestBaseAgents.java: -------------------------------------------------------------------------------- 1 | package utils.analysis; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Set; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Wrapper around collected data on the best base agents in all games 14 | * 15 | * TODO could probably make sure we only ever load the data once... 16 | * 17 | * @author Dennis Soemers 18 | */ 19 | public class BestBaseAgents 20 | { 21 | 22 | //------------------------------------------------------------------------- 23 | 24 | /** Map of entries (mapping from cleaned game names to entries of data) */ 25 | private final Map entries; 26 | 27 | //------------------------------------------------------------------------- 28 | 29 | /** 30 | * @return Loads and returns the analysed data as stored so far. 31 | */ 32 | public static BestBaseAgents loadData() 33 | { 34 | final Map entries = new HashMap(); 35 | final File file = new File("../AI/resources/Analysis/BestBaseAgents.csv"); 36 | 37 | try (final BufferedReader reader = new BufferedReader(new FileReader(file))) 38 | { 39 | reader.readLine(); // headers line, which we don't use 40 | 41 | for (String line; (line = reader.readLine()) != null; /**/) 42 | { 43 | final String[] lineSplit = line.split(Pattern.quote(",")); 44 | entries.put(lineSplit[2], new Entry 45 | ( 46 | lineSplit[0], 47 | lineSplit[1], 48 | lineSplit[2], 49 | lineSplit[3], 50 | Float.parseFloat(lineSplit[4]) 51 | )); 52 | } 53 | } 54 | catch (final IOException e) 55 | { 56 | e.printStackTrace(); 57 | } 58 | 59 | return new BestBaseAgents(entries); 60 | } 61 | 62 | /** 63 | * Constructor 64 | * @param entries 65 | */ 66 | private BestBaseAgents(final Map entries) 67 | { 68 | this.entries = entries; 69 | } 70 | 71 | //------------------------------------------------------------------------- 72 | 73 | /** 74 | * @param cleanGameName 75 | * @return Stored entry for given game name 76 | */ 77 | public Entry getEntry(final String cleanGameName) 78 | { 79 | return entries.get(cleanGameName); 80 | } 81 | 82 | /** 83 | * @return Set of all game keys in our file 84 | */ 85 | public Set keySet() 86 | { 87 | return entries.keySet(); 88 | } 89 | 90 | //------------------------------------------------------------------------- 91 | 92 | /** 93 | * An entry with data for one game in our collected data. 94 | * 95 | * @author Dennis Soemers 96 | */ 97 | public static class Entry 98 | { 99 | 100 | /** Name of game for which we stored data */ 101 | private final String gameName; 102 | 103 | /** Name of ruleset for which we stored data */ 104 | private final String rulesetName; 105 | 106 | /** Name of game+ruleset for which we stored data */ 107 | private final String gameRulesetName; 108 | 109 | /** String description of top agent */ 110 | private final String topAgent; 111 | 112 | /** Win percentage of the top agent */ 113 | private final float topScore; 114 | 115 | /** 116 | * Constructor 117 | * @param cleanGameName 118 | * @param rulesetName 119 | * @param gameRulesetName 120 | * @param topAgent 121 | * @param topScore 122 | */ 123 | protected Entry 124 | ( 125 | final String gameName, 126 | final String rulesetName, 127 | final String gameRulesetName, 128 | final String topAgent, 129 | final float topScore 130 | ) 131 | { 132 | this.gameName = gameName; 133 | this.rulesetName = rulesetName; 134 | this.gameRulesetName = gameRulesetName; 135 | this.topAgent = topAgent; 136 | this.topScore = topScore; 137 | } 138 | 139 | /** 140 | * @return Name of game for which we stored data 141 | */ 142 | public String gameName() 143 | { 144 | return gameName; 145 | } 146 | 147 | /** 148 | * @return Name of ruleset for which we stored data 149 | */ 150 | public String rulesetName() 151 | { 152 | return rulesetName; 153 | } 154 | 155 | /** 156 | * @return Name of game+ruleset for which we stored data 157 | */ 158 | public String gameRulesetName() 159 | { 160 | return gameRulesetName; 161 | } 162 | 163 | /** 164 | * @return String description of top agent 165 | */ 166 | public String topAgent() 167 | { 168 | return topAgent; 169 | } 170 | 171 | /** 172 | * @return Win percentage of the top agent 173 | */ 174 | public float topScore() 175 | { 176 | return topScore; 177 | } 178 | 179 | } 180 | 181 | //------------------------------------------------------------------------- 182 | 183 | } 184 | -------------------------------------------------------------------------------- /AI/src/search/mcts/selection/NoisyAG0Selection.java: -------------------------------------------------------------------------------- 1 | package search.mcts.selection; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import main.collections.FVector; 6 | import other.state.State; 7 | import search.mcts.MCTS; 8 | import search.mcts.nodes.BaseNode; 9 | 10 | /** 11 | * A noisy variant of the AlphaGo Zero selection phase; mixes the prior 12 | * policy with a uniform policy. 13 | * 14 | * @author Dennis Soemers 15 | * 16 | */ 17 | public final class NoisyAG0Selection implements SelectionStrategy 18 | { 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** Exploration constant for AlphaGo Zero's selection strategy */ 23 | protected double explorationConstant; 24 | 25 | /** Weight to assign to the uniform distribution */ 26 | protected double uniformDistWeight; 27 | 28 | //------------------------------------------------------------------------- 29 | 30 | /** 31 | * Constructor with default exploration constant of 2.5 and weight of 0.25 32 | * for the uniform distribution. 33 | */ 34 | public NoisyAG0Selection() 35 | { 36 | this(2.5, 0.25); 37 | } 38 | 39 | /** 40 | * Constructor with custom hyperparams 41 | * @param explorationConstant 42 | */ 43 | public NoisyAG0Selection(final double explorationConstant, final double uniformDistWeight) 44 | { 45 | this.explorationConstant = explorationConstant; 46 | this.uniformDistWeight = uniformDistWeight; 47 | } 48 | 49 | //------------------------------------------------------------------------- 50 | 51 | @Override 52 | public int select(final MCTS mcts, final BaseNode current) 53 | { 54 | int bestIdx = -1; 55 | double bestValue = Double.NEGATIVE_INFINITY; 56 | int numBestFound = 0; 57 | 58 | final int numChildren = current.numLegalMoves(); 59 | final FVector distribution = current.learnedSelectionPolicy().copy(); 60 | distribution.mult((float) (1.0 - uniformDistWeight)); 61 | final FVector uniformDist = new FVector(numChildren); 62 | uniformDist.fill(0, numChildren, (float)(uniformDistWeight / numChildren)); 63 | distribution.add(uniformDist); 64 | 65 | final double parentSqrt = Math.sqrt(current.sumLegalChildVisits()); 66 | 67 | final State state = current.contextRef().state(); 68 | final int moverAgent = state.playerToAgent(state.mover()); 69 | final double unvisitedValueEstimate = 70 | current.valueEstimateUnvisitedChildren(moverAgent); 71 | 72 | for (int i = 0; i < numChildren; ++i) 73 | { 74 | final BaseNode child = current.childForNthLegalMove(i); 75 | final double exploit; 76 | final int numVisits; 77 | 78 | if (child == null) 79 | { 80 | exploit = unvisitedValueEstimate; 81 | numVisits = 0; 82 | } 83 | else 84 | { 85 | exploit = child.exploitationScore(moverAgent); 86 | numVisits = child.numVisits() + child.numVirtualVisits(); 87 | } 88 | 89 | final float priorProb = distribution.get(i); 90 | final double explore = (parentSqrt == 0.0) ? 1.0 : parentSqrt / (1.0 + numVisits); 91 | 92 | final double pucb1Value = exploit + explorationConstant * priorProb * explore; 93 | 94 | if (pucb1Value > bestValue) 95 | { 96 | bestValue = pucb1Value; 97 | bestIdx = i; 98 | numBestFound = 1; 99 | } 100 | else if (pucb1Value == bestValue && ThreadLocalRandom.current().nextInt() % ++numBestFound == 0) 101 | { 102 | bestIdx = i; 103 | } 104 | } 105 | 106 | return bestIdx; 107 | } 108 | 109 | //------------------------------------------------------------------------- 110 | 111 | @Override 112 | public int backpropFlags() 113 | { 114 | return 0; 115 | } 116 | 117 | @Override 118 | public int expansionFlags() 119 | { 120 | return 0; 121 | } 122 | 123 | @Override 124 | public void customise(final String[] inputs) 125 | { 126 | if (inputs.length > 1) 127 | { 128 | // we have more inputs than just the name of the strategy 129 | for (int i = 1; i < inputs.length; ++i) 130 | { 131 | final String input = inputs[i]; 132 | 133 | if (input.startsWith("explorationconstant=")) 134 | { 135 | explorationConstant = Double.parseDouble(input.substring("explorationconstant=".length())); 136 | } 137 | else if (input.startsWith("uniformdistweight=")) 138 | { 139 | uniformDistWeight = Double.parseDouble(input.substring("uniformdistweight=".length())); 140 | } 141 | else 142 | { 143 | System.err.println("NoisyAG0Selection ignores unknown customisation: " + input); 144 | } 145 | } 146 | } 147 | } 148 | 149 | //------------------------------------------------------------------------- 150 | 151 | } 152 | -------------------------------------------------------------------------------- /AI/src/function_approx/LinearFunction.java: -------------------------------------------------------------------------------- 1 | package function_approx; 2 | import java.io.BufferedReader; 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.io.PrintWriter; 8 | 9 | import features.FeatureVector; 10 | import features.WeightVector; 11 | import gnu.trove.list.array.TFloatArrayList; 12 | import main.collections.FVector; 13 | 14 | /** 15 | * A linear function approximator 16 | * 17 | * @author Dennis Soemers 18 | */ 19 | public class LinearFunction 20 | { 21 | 22 | //------------------------------------------------------------------------- 23 | 24 | /** Our vector of parameters / weights */ 25 | protected WeightVector theta; 26 | 27 | /** Filepath for feature set corresponding to our parameters */ 28 | protected String featureSetFile = null; 29 | 30 | //------------------------------------------------------------------------- 31 | 32 | /** 33 | * Constructor 34 | * 35 | * @param theta 36 | */ 37 | public LinearFunction(final WeightVector theta) 38 | { 39 | this.theta = theta; 40 | } 41 | 42 | //------------------------------------------------------------------------- 43 | 44 | /** 45 | * @param featureVector 46 | * @return Predicted value for a given feature vector 47 | */ 48 | public float predict(final FeatureVector featureVector) 49 | { 50 | return effectiveParams().dot(featureVector); 51 | } 52 | 53 | /** 54 | * @return Vector of effective parameters, used for making predictions. For this 55 | * class, a reference to theta. 56 | */ 57 | public WeightVector effectiveParams() 58 | { 59 | return theta; 60 | } 61 | 62 | /** 63 | * @return Reference to parameters vector that we can train. For this class, 64 | * a reference to theta. 65 | */ 66 | public WeightVector trainableParams() 67 | { 68 | return theta; 69 | } 70 | 71 | //------------------------------------------------------------------------- 72 | 73 | /** 74 | * Replaces the linear function's param vector theta 75 | * @param newTheta 76 | */ 77 | public void setTheta(final WeightVector newTheta) 78 | { 79 | theta = newTheta; 80 | } 81 | 82 | //------------------------------------------------------------------------- 83 | 84 | /** 85 | * @return Filename for corresponding Feature Set 86 | */ 87 | public String featureSetFile() 88 | { 89 | return featureSetFile; 90 | } 91 | 92 | /** 93 | * Sets the filename for the corresponding Feature Set 94 | * @param featureSetFile 95 | */ 96 | public void setFeatureSetFile(final String featureSetFile) 97 | { 98 | this.featureSetFile = featureSetFile; 99 | } 100 | 101 | //------------------------------------------------------------------------- 102 | 103 | /** 104 | * Writes linear function to the given filepath 105 | * @param filepath 106 | * @param featureSetFiles 107 | */ 108 | public void writeToFile(final String filepath, final String[] featureSetFiles) 109 | { 110 | try (final PrintWriter writer = new PrintWriter(filepath, "UTF-8")) 111 | { 112 | for (int i = 0; i < theta.allWeights().dim(); ++i) 113 | { 114 | writer.println(theta.allWeights().get(i)); 115 | } 116 | 117 | for (final String fsf : featureSetFiles) 118 | { 119 | writer.println("FeatureSet=" + new File(fsf).getName()); 120 | } 121 | } 122 | catch (final IOException e) 123 | { 124 | e.printStackTrace(); 125 | } 126 | } 127 | 128 | /** 129 | * @param filepath 130 | * @return Reads linear function from the given filepath 131 | */ 132 | public static LinearFunction fromFile(final String filepath) 133 | { 134 | try 135 | ( 136 | final BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filepath), "UTF-8")) 137 | ) 138 | { 139 | final TFloatArrayList readFloats = new TFloatArrayList(); 140 | String featureSetFile = null; 141 | 142 | while (true) 143 | { 144 | final String line = reader.readLine(); 145 | 146 | if (line == null) 147 | break; 148 | 149 | if (line.startsWith("FeatureSet=")) 150 | featureSetFile = line.substring("FeatureSet=".length()); 151 | else 152 | readFloats.add(Float.parseFloat(line)); 153 | } 154 | 155 | float[] floats = new float[readFloats.size()]; 156 | 157 | for (int i = 0; i < floats.length; ++i) 158 | { 159 | floats[i] = readFloats.getQuick(i); 160 | } 161 | 162 | final LinearFunction func = new LinearFunction(new WeightVector(FVector.wrap(floats))); 163 | func.setFeatureSetFile(featureSetFile); 164 | 165 | return func; 166 | } 167 | catch (final Exception e) 168 | { 169 | System.err.println("exception while trying to load from filepath: " + filepath); 170 | e.printStackTrace(); 171 | } 172 | 173 | return null; 174 | } 175 | 176 | //------------------------------------------------------------------------- 177 | 178 | } 179 | -------------------------------------------------------------------------------- /AI/src/search/mcts/selection/ExItSelection.java: -------------------------------------------------------------------------------- 1 | package search.mcts.selection; 2 | 3 | import java.util.concurrent.ThreadLocalRandom; 4 | 5 | import main.collections.FVector; 6 | import other.state.State; 7 | import search.mcts.MCTS; 8 | import search.mcts.nodes.BaseNode; 9 | 10 | /** 11 | * Selection strategy used by Anthony, Tian, and Barber (2017) for 12 | * Expert Iteration 13 | * 14 | * @author Dennis Soemers 15 | * 16 | */ 17 | public final class ExItSelection implements SelectionStrategy 18 | { 19 | 20 | //------------------------------------------------------------------------- 21 | 22 | /** The standard exploration constant of UCB1 */ 23 | protected double explorationConstant; 24 | 25 | /** 26 | * Weight parameter for the prior policy term (w_a in the ExIt paper) 27 | * 28 | * Note: paper mentions a good value for this hyperparameter may be 29 | * close to the average number of simulations per action at the root... 30 | * which is going to wildly vary per game and per time-control setting. 31 | */ 32 | protected double priorPolicyWeight; 33 | 34 | //------------------------------------------------------------------------- 35 | 36 | /** 37 | * Constructor 38 | * @param priorPolicyWeight 39 | */ 40 | public ExItSelection(final double priorPolicyWeight) 41 | { 42 | this(Math.sqrt(2.0), priorPolicyWeight); 43 | } 44 | 45 | /** 46 | * Constructor 47 | * @param explorationConstant 48 | * @param priorPolicyWeight 49 | */ 50 | public ExItSelection(final double explorationConstant, final double priorPolicyWeight) 51 | { 52 | this.explorationConstant = explorationConstant; 53 | this.priorPolicyWeight = priorPolicyWeight; 54 | } 55 | 56 | //------------------------------------------------------------------------- 57 | 58 | @Override 59 | public int select(final MCTS mcts, final BaseNode current) 60 | { 61 | int bestIdx = -1; 62 | double bestValue = Double.NEGATIVE_INFINITY; 63 | int numBestFound = 0; 64 | 65 | final FVector distribution = current.learnedSelectionPolicy(); 66 | final double parentLog = Math.log(Math.max(1, current.sumLegalChildVisits())); 67 | 68 | final int numChildren = current.numLegalMoves(); 69 | final State state = current.contextRef().state(); 70 | final int moverAgent = state.playerToAgent(state.mover()); 71 | final double unvisitedValueEstimate = 72 | current.valueEstimateUnvisitedChildren(moverAgent); 73 | 74 | for (int i = 0; i < numChildren; ++i) 75 | { 76 | final BaseNode child = current.childForNthLegalMove(i); 77 | final double exploit; 78 | final double explore; 79 | final int numVisits; 80 | 81 | if (child == null) 82 | { 83 | exploit = unvisitedValueEstimate; 84 | numVisits = 0; 85 | explore = Math.sqrt(parentLog); 86 | } 87 | else 88 | { 89 | exploit = child.exploitationScore(moverAgent); 90 | numVisits = child.numVisits() + child.numVirtualVisits(); 91 | explore = Math.sqrt(parentLog / numVisits); 92 | } 93 | 94 | final float priorProb = distribution.get(i); 95 | final double priorTerm = priorProb / (numVisits + 1); 96 | 97 | final double ucb1pValue = 98 | exploit + 99 | explorationConstant * explore + 100 | priorPolicyWeight * priorTerm; 101 | 102 | if (ucb1pValue > bestValue) 103 | { 104 | bestValue = ucb1pValue; 105 | bestIdx = i; 106 | numBestFound = 1; 107 | } 108 | else if 109 | ( 110 | ucb1pValue == bestValue 111 | && 112 | ThreadLocalRandom.current().nextInt() % ++numBestFound == 0 113 | ) 114 | { 115 | bestIdx = i; 116 | } 117 | } 118 | 119 | return bestIdx; 120 | } 121 | 122 | //------------------------------------------------------------------------- 123 | 124 | @Override 125 | public int backpropFlags() 126 | { 127 | return 0; 128 | } 129 | 130 | @Override 131 | public int expansionFlags() 132 | { 133 | return 0; 134 | } 135 | 136 | @Override 137 | public void customise(final String[] inputs) 138 | { 139 | if (inputs.length > 1) 140 | { 141 | // we have more inputs than just the name of the strategy 142 | for (int i = 1; i < inputs.length; ++i) 143 | { 144 | final String input = inputs[i]; 145 | 146 | if (input.startsWith("explorationconstant=")) 147 | { 148 | explorationConstant = Double.parseDouble( 149 | input.substring("explorationconstant=".length())); 150 | } 151 | else 152 | { 153 | System.err.println("ExItSelection ignores unknown customisation: " + input); 154 | } 155 | } 156 | } 157 | } 158 | 159 | //------------------------------------------------------------------------- 160 | 161 | } 162 | -------------------------------------------------------------------------------- /AI/src/utils/data_structures/support/zhang_shasha/Main.java: -------------------------------------------------------------------------------- 1 | package utils.data_structures.support.zhang_shasha; 2 | 3 | /** 4 | * Code originally from: https://github.com/ijkilchenko/ZhangShasha 5 | * 6 | * Afterwards modified for style / various improvements 7 | * 8 | * @author Dennis Soemers 9 | */ 10 | public class Main 11 | { 12 | 13 | public static void main(String[] args) 14 | { 15 | // Sample trees (in preorder). 16 | String tree1Str1 = "f(d(a c(b)) e)"; 17 | String tree1Str2 = "f(c(d(a b)) e)"; 18 | // Distance: 2 (main example used in the Zhang-Shasha paper) 19 | 20 | String tree1Str3 = "a(b(c d) e(f g(i)))"; 21 | String tree1Str4 = "a(b(c d) e(f g(h)))"; 22 | // Distance: 1 23 | 24 | String tree1Str5 = "d"; 25 | String tree1Str6 = "g(h)"; 26 | // Distance: 2 27 | 28 | Tree tree1 = new Tree(tree1Str1); 29 | Tree tree2 = new Tree(tree1Str2); 30 | 31 | Tree tree3 = new Tree(tree1Str3); 32 | Tree tree4 = new Tree(tree1Str4); 33 | 34 | Tree tree5 = new Tree(tree1Str5); 35 | Tree tree6 = new Tree(tree1Str6); 36 | 37 | int distance1 = Tree.ZhangShasha(tree1, tree2); 38 | System.out.println("Expected 2; got " + distance1); 39 | 40 | int distance2 = Tree.ZhangShasha(tree3, tree4); 41 | System.out.println("Expected 1; got " + distance2); 42 | 43 | int distance3 = Tree.ZhangShasha(tree5, tree6); 44 | System.out.println("Expected 2; got " + distance3); 45 | 46 | //---------------------------------------- 47 | 48 | final String a = 49 | "game(TicTacToe players(a))"; 50 | final String b = 51 | "game(TicTacToe players(b))"; 52 | 53 | final Tree ta = new Tree(a); 54 | final Tree tb = new Tree(b); 55 | 56 | int dist = Tree.ZhangShasha(ta, tb); 57 | System.out.println("dist=" + dist + "."); 58 | 59 | final String ttt1 = 60 | "game(\"Tic-Tac-Toe\" "+ 61 | " players(\"2\") " + 62 | " equipment( " + 63 | " board(square(\"3\")) " + 64 | " piece(\"Disc\" P1) " + 65 | " piece(\"Cross\" P2) " + 66 | " ) " + 67 | " rules( " + 68 | " play(add(empty)) " + 69 | " end(if(isLine(\"3\") result(Mover Win))) " + 70 | " )" + 71 | ")"; 72 | final Tree treeA = new Tree(ttt1); 73 | System.out.println("treeA: " + treeA); 74 | 75 | final String ttt1a = 76 | "game(\"Tic-Tac-Toe\" "+ 77 | " players(\"2\") " + 78 | " equipment( " + 79 | " board(hexagon(\"3\")) " + 80 | " piece(\"Disc\" P1) " + 81 | " piece(\"Cross\" P2) " + 82 | " ) " + 83 | " rules( " + 84 | " play(add(empty)) " + 85 | " end(if(isLine(\"4\") result(Mover Win))) " + 86 | " )" + 87 | ")"; 88 | final Tree treeAa = new Tree(ttt1a); 89 | System.out.println("treeAa: " + treeAa); 90 | 91 | int distX = Tree.ZhangShasha(treeA, treeA); 92 | int distY = Tree.ZhangShasha(treeA, treeAa); 93 | System.out.println("distX=" + distX + ", distY=" + distY + "."); 94 | 95 | final String ttt2 = 96 | "(game \"Tic-Tac-Toe\" " + 97 | " (players 2) " + 98 | " (equipment { " + 99 | " (board (hexagon 3)) " + 100 | " (piece \"Disc\" P1) " + 101 | " (piece \"Cross\" P2) " + 102 | " }) " + 103 | " (rules " + 104 | " (play (add (empty))) " + 105 | " (end (if (isLine 3) (result Mover Win))) " + 106 | " )" + 107 | ")"; 108 | final Tree treeB = new Tree(ttt2); 109 | System.out.println("treeB: " + treeB); 110 | 111 | final String hex = 112 | "(game \"Hex\" " + 113 | " (players 2) " + 114 | " (equipment { " + 115 | " (board (rhombus 11)) " + 116 | " (piece \"Ball\" Each) " + 117 | " (regions P1 { (sites Side NE) (sites Side SW) } ) " + 118 | " (regions P2 { (sites Side NW) (sites Side SE) } ) " + 119 | " }) " + 120 | " (rules " + 121 | " (meta (swap)) " + 122 | " (play (add (empty))) " + 123 | " (end (if (isConnected Mover) (result Mover Win))) " + 124 | " ) " + 125 | ")"; 126 | final Tree treeC = new Tree(hex); 127 | System.out.println("treeC: " + treeC); 128 | 129 | int distAA = Tree.ZhangShasha(treeA, treeA); 130 | int distAB = Tree.ZhangShasha(treeA, treeB); 131 | int distAC = Tree.ZhangShasha(treeA, treeC); 132 | int distBC = Tree.ZhangShasha(treeB, treeC); 133 | int distBA = Tree.ZhangShasha(treeB, treeA); 134 | int distCA = Tree.ZhangShasha(treeC, treeA); 135 | int distCB = Tree.ZhangShasha(treeC, treeB); 136 | 137 | System.out.println("distAA=" + distAA + "."); 138 | System.out.println("distAB=" + distAB + ", distAC=" + distAC + ", distBC=" + distBC + "."); 139 | System.out.println("distBA=" + distBA + ", distCA=" + distCA + ", distCB=" + distCB + "."); 140 | 141 | } 142 | } 143 | --------------------------------------------------------------------------------