Esempio n. 1
0
        /// <summary>
        /// Add or update a entry in the hashtable.
        /// </summary>
        private void _updateOrAddHash(AIBoard cur, int code, int depth, StateInfo.ValueType flag, int value, Move best)
        {
            if (!this.transTable.ContainsKey(cur))
            {
                this.transTable[cur] = new StateInfo()
                {
                    CodeToMove = code, Depth = depth, Flag = flag, Value = value, Best = best
                };
                return;
            }

            var entry = this.transTable[cur];

            if (depth < entry.Depth)             // Don't overwrite w/ shallower entries.
            {
                return;
            }

            entry.CodeToMove = code;
            entry.Depth      = depth;
            entry.Flag       = flag;
            entry.Value      = value;

            if (best != null || entry.Best == null)
            {
                entry.Best = best;
            }
        }
Esempio n. 2
0
        // Performs an iterative deepening depth-first search, combining the concepts of
        // minimax with alpha-beta elimination and some memoization to efficiently compute
        // the net gain/loss of any given series of moves.
        //
        // α is the minimum score that the maximizing player is assured of.
        // β is the maximum score that the minimizing player is assured of.
        private int _search(Tree <Move, AIBoard> .Node node, int depth, int α, int β, bool me, List <Move> pv)
        {
            int pcode = me ? aicode : notaicode, notpcode = !me ? aicode : notaicode;

            StateInfo.ValueType valType = StateInfo.ValueType.Alpha;
            List <Move>         moves   = new List <Move>();
            Move cachedBest             = null;

            // The following only applies if we're not at the root.
            if (!object.ReferenceEquals(this.gameTree.Root, node))
            {
                // Check to see if we already explored this node currently, return draw score.
                if (this.repetitionCheck.Contains(this.workingBoard))
                {
                    return(0);
                }

                // If we have a cached answer for the score of the current state at a certain depth,
                // see if we can use it.
                if (this.transTable.ContainsKey(this.workingBoard))
                {
                    var val = this.transTable[this.workingBoard];

                    if (val.CodeToMove == pcode)
                    {
                        if (val.Depth >= depth)
                        {
                            switch (val.Flag)
                            {
                            case StateInfo.ValueType.Exact:
                                // If this was an exact value, we can return it no matter what.
                                node.Value = val.Value;

#if DEBUG
                                transHits++;                                         // Mark a transposition table hit.
                                transCuts++;
#endif

                                return(val.Value);

                            case StateInfo.ValueType.Alpha:
                                // Since alpha was stored, the value of the node was AT MOST this value.
                                // If we can cut off, do that.
                                if (val.Value <= α)
                                {
                                    node.Value = α;

#if DEBUG
                                    transHits++;                                             // Mark a transposition table hit.
                                    transCuts++;
#endif

                                    return(α);
                                }
                                break;

                            case StateInfo.ValueType.Beta:
                                // Since beta was stored, the value of the node was AT LEAST this value.
                                // If we can cut off, do that.
                                if (val.Value >= β)
                                {
                                    node.Value = β;

#if DEBUG
                                    transHits++;                                             // Mark a transposition table hit.
                                    transCuts++;
#endif

                                    return(β);
                                }
                                break;
                            }
                        }

                        // If we have a cached entry, but weren't able to cut off based on it,
                        // try to add the best move first, but only if it's still valid.
                        if (val.Best != null)
                        {
                            var m = val.Best;

                            if (this.workingBoard[m.xfrom, m.yfrom] == pcode && this.workingBoard[m.xto, m.yto] == 0)
                            {
                                cachedBest = m;

#if DEBUG
                                transHits++;                                 // Mark a transposition table hit.
#endif

                                moves.Add(m);
                            }
                        }
                    }
                }
            }

            // Next, check for an exit condition for our search function.
            // If our depth == 0, that means we are just applying our evaluation function.
            // Otherwise, we may have a game-over state.
            if (depth == 0 || this.workingBoard.GameOver)
            {
                // The 'goodness' of current state for our player will simply be his piece count.
                int score = _score(this.workingBoard, pcode, notpcode);

#if DEBUG
                evalCalls++;                 // Track a call to the evaluation function.
#endif

                // Cache the answer, and set variables.
                _updateOrAddHash(this.workingBoard, pcode, depth, StateInfo.ValueType.Exact, score, null);
                node.Value = score;

                return(score);
            }

            // Get an ordered list of moves (and hence game tree nodes) to traverse.
            // Ideally, heuristics should be applied to find the best evaluation order, but
            // that is fancy stuff.
            var moveIter = moves.Concat(_findMoves(this.workingBoard, pcode));
            // If we have no best move ordering, collapse the move list and order by gain.
            //if (cachedBest == null)
            //{
            //	moveIter = moveIter.ToList();
            //	((List<Move>) moveIter).Sort((x, y) => -x.gain.CompareTo(y.gain));
            //	testset++;
            //}
            //tzestseztzeswt++;

            // If we're searching moves, add the current board to the exploration hash set.
            this.repetitionCheck.Add(this.workingBoard);

            // We want to choose the maximum valued node, i.e. the one which gets us the highest score.
            foreach (var move in moveIter)
            {
#if DEBUG
                nodeCnt++;                 // Increment explored node count.
#endif

                // Get the next node, and modify the board accordingly.
                #region var nextNode;
                var nextNode = node;
                if (node.HasLeaf(move))
                {
                    nextNode = node[move];
                }
                //nextNode.Value = 0;
                else
                {
                    nextNode = node.AddLeaf(move);
                }
                #endregion
                var boardHash = this.workingBoard.LongHashCode;
                _executeAndPushMove(move, pcode, notpcode);

                // Get the score of the child. Since we must account for perspective switches,
                // We negate the value and reverse/negate alpha and beta.
                var pv2     = new List <Move>();
                var moveVal = -_search(nextNode, depth - 1, -β, -α, !me, pv2);

                // We searched, nothing exploded, undo the move.
                this.workingBoard.PopState();
                Debug.Assert(this.workingBoard.LongHashCode == boardHash, "Move undo was unsuccessful!");

                // If moveVal > α, then this is a decently good move. For all we know, at least.
                // Save it as our alpha. Also save the fact that this is an exact node value, if we
                // update alpha.
                if (moveVal > α)
                {
                    // If a move results in a score that is greater than or equal to β,
                    // this whole node is trash, since the opponent is not going to let the side to
                    // move achieve this position, because there is some choice the opponent can make that will avoid it.
                    // This is a fail high.
                    if (moveVal >= β)
                    {
                        _updateOrAddHash(this.workingBoard, pcode, depth, StateInfo.ValueType.Beta, β, move);

                        // Before cutting off, mark the end of our traversal.
                        this.repetitionCheck.Remove(this.workingBoard);
                        return(β);
                    }

                    // Found a new best move, update the principal variation.
                    pv.Clear();
                    pv.Add(move);
                    pv.AddRange(pv2);

                    valType    = StateInfo.ValueType.Exact;
                    cachedBest = move;

                    α = moveVal;
                }
            }

            // Done searching moves, close the current board from the repetition checker.
            this.repetitionCheck.Remove(this.workingBoard);

            // The value of the node (the MAXIMUM of its child nodes [best move for me]) is α.
            _updateOrAddHash(this.workingBoard, pcode, depth, valType, α, cachedBest);
            if (valType == StateInfo.ValueType.Exact)
            {
                node.Value = α;
            }
            return(α);
        }