Пример #1
0
            /// <summary>
            /// Sets a piece in the hash.
            /// </summary>
            ///
            /// <exception cref="System.ArgumentOutOfRangeException">If index is >= `Board.pieceCount`</exception>
            /// <exception cref="CS_Project.Game.HashException">If `allowOverwrite` is false, and there is a non-empty piece at 'index'</exception>
            ///
            /// <param name="piece">The piece to use</param>
            /// <param name="index">The index to place the piece</param>
            /// <param name="allowOverwrite">See the `HashException` part of this documentation</param>
            public void setPiece(Board.Piece piece, int index, bool allowOverwrite = false)
            {
                // Enforce the behaviour of `allowOverwrite`
                if (this.getPieceChar(index) != Hash.emptyChar && !allowOverwrite)
                {
                    throw new HashException($"Attempted to place {piece} at index {index}, however a non-null piece is there and allowOverwrite is false. Hash = {this._hash}");
                }

                // Figure out which character to use to represent `piece`.
                char pieceChar = '\0';

                if (piece == this.myPiece)
                {
                    pieceChar = Hash.myChar;
                }
                else if (piece == this.otherPiece)
                {
                    pieceChar = Hash.otherChar;
                }
                else
                {
                    pieceChar = Hash.emptyChar;
                }

                // Then place that character into the hash.
                this._hash[index] = pieceChar;
                this.checkCorrectness();
            }
Пример #2
0
 /// <summary>
 /// Updates the GUI to reflect the new state of the board.
 /// </summary>
 ///
 /// <param name="boardState">The new state of the baord.</param>
 /// <param name="turn">Who's turn it currently is.</param>
 private void updateGUI(Board.Hash boardState, Board.Piece turn)
 {
     this._window.Dispatcher.Invoke(() =>
     {
         this._window.updateBoard(boardState);
         this._window.updateText(null, (turn == base.piece) ? "It is your turn" : "The AI is thinking...");
     });
 }
Пример #3
0
        // implement Controller.onMatchStart
        public override void onMatchStart(Board board, Board.Piece myPiece)
        {
            base.onMatchStart(board, myPiece);

            // When the match starts, tell the player which piece they're using.
            this._window.Dispatcher.Invoke(() =>
            {
                this._window.updateText($"[You are {myPiece}]");
            });
        }
Пример #4
0
        /// <summary>
        /// Creates a hash of the board, using a given piece as the "myPiece".
        /// </summary>
        ///
        /// <param name="piece">The piece that should be used as the "myPiece"</param>
        private Hash createHashFor(Board.Piece piece)
        {
            var hash = new Hash(piece);

            for (var i = 0; i < this._board.Length; i++)
            {
                hash.setPiece(this._board[i], i);
            }

            return(hash);
        }
Пример #5
0
        // implement Controller.onMatchStart
        public override void onMatchStart(Board board, Board.Piece myPiece)
        {
            base.onMatchStart(board, myPiece);

            // Reset some variables.
            this._localTree = Node.root;
            this._useRandom = false;

            // Update the global tree debugger
            this.doDebugAction(() => this._globalDebug.updateNodeData(this._globalTree));
            this.doDebugAction(() => this._globalDebug.updateStatusText("[GLOBAL MOVE TREE DEBUGGER]"));
        }
Пример #6
0
            private Hash(Board.Piece myPiece, bool dummyParam)
            {
                if (myPiece == Board.Piece.Empty)
                {
                    throw new HashException("myPiece must not be Board.Piece.empty");
                }

                // Figure out who is using what piece.
                this.myPiece    = myPiece;
                this.otherPiece = (myPiece == Board.Piece.O) ? Board.Piece.X
                                                             : Board.Piece.O;
            }
Пример #7
0
        /// <summary>
        /// Evaluates the board based on how many pieces of a kind there are in a row. The more a player has in a row, the greater their score will be.
        /// </summary>
        /// <param name="b">The board that will be evaluated for a score.</param>
        /// <param name="p">The piece on the board in which the score is taken in respect to.</param>
        /// <returns>The total score of the board in respect to the piece.</returns>
        private float EvaluateScore(Board b, Board.Piece p)
        {
            float score = 0;

            //The opposing piece is called other. If the parameter p is Player 1, then other is Player 2. If p is Player 2, then other is player 1.
            Board.Piece other = (p == Board.Piece.Player1 ? Board.Piece.Player2 : Board.Piece.Player1);

            //The score from each type of pairing. The count of p is positive and the opponent is negative.
            score += (b.countSingles(p) - b.countSingles(other)) * (int)Point.One;
            score += (b.countDoubles(p) - b.countDoubles(other)) * (int)Point.Two;
            score += (b.countTriples(p) - b.countTriples(other)) * (int)Point.Three;
            score += (b.countQuadruples(p) - b.countQuadruples(other)) * (int)Point.Four;

            return(score);
        }
Пример #8
0
            /// <summary>
            /// Gets the board piece at a certain index.
            /// </summary>
            ///
            /// <exception cref="System.ArgumentOutOfRangeException">If index is >= `Board.pieceCount`</exception>
            ///
            /// <param name="index">The index to use</param>
            ///
            /// <returns>The board piece at 'index'</returns>
            public Board.Piece getPiece(int index)
            {
                Board.Piece piece     = Board.Piece.Empty;
                var         pieceChar = this.getPieceChar(index);

                // Convert the character into a Board.Piece
                switch (pieceChar)
                {
                case Hash.emptyChar: piece = Board.Piece.Empty; break;

                case Hash.myChar:    piece = this.myPiece;      break;

                case Hash.otherChar: piece = this.otherPiece;   break;

                default: Debug.Assert(false, "This should not have happened"); break;
                }

                return(piece);
            }
Пример #9
0
        // --------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// The AI managing method that uses Alphabeta Minimax. Will set the bestMove field.
        /// </summary>
        /// <param name="alpha">Lower bound of the optimal score.</param>
        /// <param name="beta">Upper bound of the optimal score.</param>
        /// <param name="maxDepth">The maximum number of levels deep in the recursion.</param>
        /// <param name="currentDepth">The current number of the level in the recursion. Set to 0 initially to start.</param>
        /// <param name="player">The bestMove in respect to the player.</param>
        /// <param name="tempBoard">The current board state.</param>
        /// <returns>End result of the recursion of the bestScore, ie. the best score possible.</returns>
        private float ComputerMove(float alpha, float beta, int maxDepth, int currentDepth, Board.Piece player, Board tempBoard)
        {
            // base case
            if (board.countQuadruples(player) > 0 || currentDepth == maxDepth)
            {
                //Debug.Log("Board Score : " + EvaluateScore(tempBoard, player));
                return(EvaluateScore(tempBoard, player));
            }

            // set initial values
            float bestScore;
            float tempScore;
            int   row;

            Board.Piece other = (player == Board.Piece.Player1 ? Board.Piece.Player2 : Board.Piece.Player1);

            //If this is true then it means that it is the maximizing player's turn.
            if (currentDepth % 2 == 0)
            {
                //bestScore has to be overwritten with a larger number, therefore it starts at smallest possible.
                bestScore = Mathf.NegativeInfinity;
                foreach (int move in board.getPossibleMoves())
                {
                    tempScore = bestScore;

                    row   = tempBoard.getEmptyCell(move);
                    alpha = Mathf.Max(alpha, bestScore);
                    tempBoard.setCell(move, row, player);

                    bestScore = Mathf.Max(bestScore, ComputerMove(-beta, -alpha, maxDepth, currentDepth + 1, player, tempBoard));
                    if (currentDepth == 0)
                    {
                        bestMove = tempScore == bestScore ? bestMove : move;
                    }

                    tempBoard.setCell(move, row, Board.Piece.Empty);

                    if (beta <= alpha)
                    {
                        break;
                    }
                }
            }

            //The opponent's turn or the minimizing player's turn.
            else
            {
                //bestScore has to be overwritten with a smaller number, therefore it starts at the largest number possible.
                bestScore = Mathf.Infinity;
                foreach (int move in board.getPossibleMoves())
                {
                    tempScore = bestScore;

                    row = tempBoard.getEmptyCell(move);
                    tempBoard.setCell(move, row, other);

                    bestScore = Mathf.Min(bestScore, ComputerMove(-beta, -alpha, maxDepth, currentDepth + 1, player, tempBoard));
                    if (currentDepth == 0)
                    {
                        bestMove = bestMove = tempScore == bestScore ? bestMove : move;
                    }

                    beta = Mathf.Min(beta, bestScore);

                    tempBoard.setCell(move, row, Board.Piece.Empty);
                    if (beta <= alpha)
                    {
                        break;
                    }
                }
            }

            Debug.Log("Best Move : " + bestMove);
            //Debug.Log("Best Score : " + bestScore);
            return(bestScore);
        }
Пример #10
0
 /// <summary>
 /// Called whenever the match has ended.
 ///
 /// Notes for inheriting classes: Call 'super.onMatchEnd' only at the end of the function.
 /// </summary>
 ///
 /// <param name="boardState">The final state of the board.</param>
 /// <param name="index">The index of where the last piece was placed on the board.</param>
 /// <param name="result">Contains the match result.</param>
 public virtual void onMatchEnd(Board.Hash boardState, int index, MatchResult result)
 {
     this.board = null;
     this.piece = Board.Piece.Empty;
 }
Пример #11
0
 /// <summary>
 /// Called whenever a new match is started.
 /// </summary>
 ///
 /// <param name="board">The board that is using this controller.</param>
 /// <param name="myPiece">Which piece this controller has been given.</param>
 public virtual void onMatchStart(Board board, Board.Piece myPiece)
 {
     this.board = board;
     this.piece = myPiece;
 }
Пример #12
0
            // implement ISerialiseable.deserialise
            public void deserialise(BinaryReader input, uint version)
            {
                // TREE version 1
                if (version == 1)
                {
                    var length = input.ReadByte();
                    this._hash      = input.ReadChars(length);
                    this.myPiece    = (Board.Piece)input.ReadByte();
                    this.otherPiece = (Board.Piece)input.ReadByte();
                }

                /**
                 * Format of a hash: (TREE version 2)
                 * [3 bytes]
                 * byte 1: 4433 2211
                 * byte 2: 8877 6655
                 * byte 3: 0000 MO99
                 *
                 * Numbers such as '11' and '55' represent the Board.Piece in slot '1' and '5', respectively.
                 * 'M' and 'O' represent the Board.Piece of 'My' piece and 'Other' piece, respectively.
                 * '0' Represents 'unused'
                 *
                 * For 'M' and 'O':
                 *   0 = Board.Piece.O
                 *   1 = Board.Piece.X
                 *
                 *   So if the 'MO' bits were 0x40: M = O, O = X
                 *   If 'MO' were 0x80:             M = X, O = O
                 *
                 * For '11' to '99':
                 *   0 = Empty
                 *   1 = M
                 *   2 = O
                 * **/
                if (version == 2)
                {
                    var bytes        = input.ReadBytes(3);
                    var identityBits = bytes[2] & 0xC; // Identity = The bits defining 'myPiece' and 'otherPiece'. 0xC = 1100
                    this.myPiece    = (identityBits & 0x4) == 0x4 ? Piece.O : Piece.X;
                    this.otherPiece = (identityBits & 0x4) == 0x4 ? Piece.X : Piece.O;

                    for (int i = 0; i < Board.pieceCount; i++)
                    {
                        var byteIndex = (i * 2) / 8;                // Index into 'bytes' for which byte to use.
                        var bitOffset = (i * 2) % 8;                // The offset into the byte to write the data to.
                        var byte_     = bytes[byteIndex];
                        var piece     = (byte_ >> bitOffset) & 0x3; // 0x3 == 0000 0011

                        switch (piece)
                        {
                        case 0: this._hash[i] = Hash.emptyChar; break;

                        case 1: this._hash[i] = Hash.myChar; break;

                        case 2: this._hash[i] = Hash.otherChar; break;

                        default:
                            throw new IOException("");
                        }
                    }
                }

                this.checkCorrectness();
            }
Пример #13
0
 /// <summary>
 /// Constructs a new Hash from a given hash string.
 /// </summary>
 ///
 /// <exception cref="CS_Project.Game.HashException">If `myPiece` is `Board.Piece.empty`</exception>
 ///
 /// <param name="myPiece">The piece that you are using, this is needed so the class knows how to correctly format the hash.</param>
 /// <param name="hash">
 ///     The hash string to use.
 ///
 ///     An internal check is made with every function call, that determines if the hash is still correct:
 ///         * The hash's length must be the same as 'Board.pieceCount'
 ///         * The hash's characters must only be made up of 'Hash.myChar', 'Hash.otherChar', and 'Hash.emptyChar'.
 ///
 ///     If the given hash fails to meet any of these checks, then an error box will be displayed.
 ///     In the future, when I can be bothered, exceptions will be thrown instead so the errors can actually be handled.
 /// </param>
 public Hash(Board.Piece myPiece, IEnumerable <char> hash) : this(myPiece, false)
 {
     this._hash = hash.ToArray();
     this.checkCorrectness();
 }
Пример #14
0
 /// <summary>
 /// Constructs a new Hash.
 /// </summary>
 ///
 /// <exception cref="CS_Project.Game.HashException">If `myPiece` is `Board.Piece.empty`</exception>
 ///
 /// <param name="myPiece">The piece that you are using, this is needed so the class knows how to correctly format the hash.</param>
 public Hash(Board.Piece myPiece) : this(myPiece, new string(Hash.emptyChar, 9))
 {
 }
Пример #15
0
        /// <summary>
        /// Starts a match between two controllers.
        ///
        /// Note to self: Run all of this stuff in a seperate thread, otherwise the GUI will freeze.
        /// Use System.Collections.Concurrent.ConcurrentQueue to talk between the two threads.
        /// </summary>
        ///
        /// <param name="xCon">The controller for the X piece.</param>
        /// <param name="oCon">The controller for the O piece.</param>
        public void startMatch(Controller xCon, Controller oCon)
        {
            Debug.Assert(this._stage == Stage.NoMatch, "Attempted to start a match while another match is in progress.");

            #region Setup controllers.
            Debug.Assert(xCon != null, "The X controller is null.");
            Debug.Assert(oCon != null, "The O controller is null.");
            this._stage = Stage.Initialisation;

            // Inform the controllers what piece they're using.
            xCon.onMatchStart(this, Piece.X);
            oCon.onMatchStart(this, Piece.O);

            // Reset some stuff
            this._lastIndex = int.MaxValue;
            #endregion

            #region Match turn logic
            Board.Piece turnPiece = Piece.O;          // The piece of who's turn it is.
            Board.Piece wonPiece  = Piece.Empty;      // The piece of who's won. Empty for no win.
            bool        isTie     = false;
            while (wonPiece == Piece.Empty && !isTie) // While there hasn't been a tie, and no one has won yet.
            {
                // Unset some flags
                this._flags &= ~Flags.HasSetPiece;

                #region Do controller turn
                this._stage = Stage.InControllerTurn;
                var hash       = this.createHashFor(turnPiece);         // Create a hash from the point of view of who's turn it is.
                var controller = (turnPiece == Piece.X) ? xCon : oCon;  // Figure out which controller to use this turn.
                this._current = controller;

                controller.onDoTurn(hash, this._lastIndex); // Allow the controller to perform its turn.
                Debug.Assert((this._flags & Flags.HasSetPiece) != 0,
                             $"The controller using the {turnPiece} piece didn't place a piece.");
                #endregion

                #region Do after controller turn
                this._stage = Stage.AfterControllerTurn;
                hash        = this.createHashFor(turnPiece);   // Create another hash for the controller
                controller.onAfterTurn(hash, this._lastIndex); // And let the controller handle its 'after move' logic
                #endregion

                #region Misc stuff
                wonPiece  = this.checkForWin(out isTie);                // See if someone's won/tied yet.
                turnPiece = (turnPiece == Piece.X) ? Piece.O : Piece.X; // Change who's turn it is
                #endregion
            }
            #endregion
            Debug.Assert(wonPiece != Piece.Empty || isTie, "There was no win condition, but the loop still ended.");

            #region Process the win
            // Create a hash for both controllers, then tell them whether they tied, won, or lost.
            var stateX = this.createHashFor(Piece.X);
            var stateO = this.createHashFor(Piece.O);
            if (isTie)
            {
                xCon.onMatchEnd(stateX, this._lastIndex, MatchResult.Tied);
                oCon.onMatchEnd(stateO, this._lastIndex, MatchResult.Tied);
            }
            else if (wonPiece == Piece.O)
            {
                xCon.onMatchEnd(stateX, this._lastIndex, MatchResult.Lost);
                oCon.onMatchEnd(stateO, this._lastIndex, MatchResult.Won);
            }
            else
            {
                xCon.onMatchEnd(stateX, this._lastIndex, MatchResult.Won);
                oCon.onMatchEnd(stateO, this._lastIndex, MatchResult.Lost);
            }
            #endregion

            #region Reset variables
            this._stage   = Stage.NoMatch;
            this._current = null;

            for (var i = 0; i < this._board.Length; i++)
            {
                this._board[i] = Piece.Empty;
            }
            #endregion
        }