2

わかりましたので、これが少し長くなることを前もって認めます。私は C# 用のチェス エンジンを作成しており、最終的な目標は UCI の実装です。ボードを指定すると、エンジンがすべての有効な動きのリストを生成するところまで来ました。しかし、私の評価コードは苦労しているようです。それ自体に対してゲームをプレイするとき、エンジンはどちらかの側で 2 つのポーンを動かし、どちらかの側でピースを前後に動かすだけだからです。私の質問に答えるのに役立つことを願って、私のコードが呼び出されて使用される条件を最もよく理解できるように、以下のプログラムの重要な部分の概要を説明します。

これは、私のインターフェイスによって呼び出されるメイン メソッドに過ぎず、ここで興味深いものは何もありません。

class BreezeEngine
{
    // Declares Piece Values
    public const int pawn = 1, knight = 4, bishop = 6, rook = 8, queen = 16, king = 60;
    public const int toDepth = 4;
    public static void BoardGen(string mode)
    {
        Board chessBoard = new Board(new int[8, 8] {
            { 8, 4, 6,16,60, 6, 4, 8 },
            { 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 0, 0, 0, 0, 0, 0, 0 },
            { 0, 0, 0, 0, 0, 0, 0, 0 },
            { 0, 0, 0, 0, 0, 0, 0, 0 },
            { 0, 0, 0, 0, 0, 0, 0, 0 },
            {-1,-1,-1,-1,-1,-1,-1,-1 },
            {-8,-4,-6,-16,-60,-6,-4,-8 }
            }, 0, true, true, true);
        PlayGame(chessBoard, true);
        return;
    }

この方法はうまく機能しています。x1、y1、x2、y2、重量の形式で移動のリストを返します。このメソッドが生成する重量は、殺されたピースの値です。ご不明な点がございましたら、お知らせください。

    private static List<int[]> CalcFutures(Board chessBoard)
    {
        // Move generation stuff.
    }

このメソッドはまだ完全ではありませんが (キャスリングやアンパッサンを処理しないため)、基本的には、特定の移動から新しいボード オブジェクトを生成するだけです。

    private static Board MoveToBoard(int[] move, Board board)
    {
        int[,] newBoard = new int[8, 8];
        Array.Copy(board.Pieces(), newBoard, 64);
        newBoard[move[3], move[2]] = newBoard[move[1], move[0]];
        newBoard[move[1], move[0]] = 0;
        if (newBoard[move[3], move[2]] == pawn && move[3] == 7) newBoard[move[3], move[2]] = queen;
        if (newBoard[move[3], move[2]] == -pawn && move[3] == 0) newBoard[move[3], move[2]] = -queen;
        return new Board(newBoard, board.Depth() + 1, !board.IsTurn(), true, true);
    }

このコードはおそらく必要ありませんが、ここのタイプミスがバグの原因である可能性を考慮して含めています。これは、エンジンに対してゲームをプレイしたり、エンジン自体を再生したりするための非常に基本的なユーザー インターフェイスです。

    private static void PlayGame(Board chessBoard, bool demo)
    {
        int[] move = new int[5];
        if (!(chessBoard.IsTurn() || demo))
        {
            Console.WriteLine("Type in your move one integer at a time: x1,y1,x2,y2");
            move[0] = Convert.ToInt32(Console.ReadLine());
            move[1] = Convert.ToInt32(Console.ReadLine());
            move[2] = Convert.ToInt32(Console.ReadLine());
            move[3] = Convert.ToInt32(Console.ReadLine());
        }
        else
        {
            Console.WriteLine("Calculating Move..." + chessBoard.IsTurn());
            move = Evaluate(CalcFutures(chessBoard), chessBoard);
        }
        if (Math.Abs(chessBoard.Pieces()[move[3], move[2]]) == king)
        {
            if (chessBoard.IsTurn()) Console.Write("White Wins");
            else Console.Write("Black Wins");
            return;
        }
        chessBoard = MoveToBoard(move, chessBoard);
        chessBoard.SetDepth(0);

        for (int i = 0; i < 8; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                Console.Write(chessBoard.Pieces()[i, j].ToString().PadLeft(3, ' '));
            }
            Console.WriteLine();
        }

        PlayGame(chessBoard, demo);
    }
}

ここで、評価アルゴリズム自体を提示する前に、簡単に触れておきます。これは、コード全体で何度も参照されているボード オブジェクトです。これには、チェス盤の配列と、ゲームの現在の状態を定義するために必要なその他の変数が含まれています。

class Board
{
    bool isTurn;
    bool castling;
    bool enemyCastling;
    int[,] pieces = new int[8, 8];
    int weight = 0;
    int depth;
    public Board(int[,] inBoard, int inDepth, bool inIsTurn, bool inCastling, bool inEnemyCastling)
    {
        Array.Copy(inBoard, pieces, 64);
        isTurn = inIsTurn;
        castling = inCastling;
        enemyCastling = inEnemyCastling;
        depth = inDepth;
    }
    public int Weight()
    {
        int sum = 0;
        foreach (int i in pieces)
            sum += i;
        weight = sum;
        return weight;
    }
    public int[,] Pieces() { return pieces; }
    public bool IsTurn() { return isTurn; }
    public void ToggleTurn() { isTurn = !isTurn; return; }
    public int Depth() { return depth; }
    public void SetDepth(int inDepth)
    {
        depth = inDepth;
    }
}

プログラムの残りの部分を概説したので、評価方法そのものを次に示します。コードは動きのリストを受け取り、それが検索されるはずの最も深い深さまである場合は、絶対値が最大のものを返すだけです。一番下にない場合は、受け取った先物リストの動きごとに単に先物リストを生成し、自分自身を再度呼び出します。次に、返された値が元の移動の重みに追加され、これまでに見つかった最良の移動と比較されます。しかし、私はこのアプローチで問題を抱えていました.negamaxがどのように機能するかを誤解したか、途中でタイプミスをしたためだと思います. 何が起こっているのか分かりますか?

    private static int[] Evaluate(List<int[]> futures, Board chessBoard)
    {
        int[] bestMove = new int[5];
        bestMove[0] = 30;
        if (chessBoard.Depth() >= toDepth)
        {
            foreach (int[] move in futures)
            {
                if (Math.Abs(move[4]) > Math.Abs(bestMove[4]))
                {
                    Array.Copy(move, bestMove, 5);
                }
            }
        }
        else
        {
            foreach (int[] move in futures)
            {
                Board newBoard = MoveToBoard(move, chessBoard);
                int[] testMove = Evaluate(CalcFutures(newBoard), newBoard);
                move[4] += testMove[4];
                if (bestMove[0] == 30) bestMove = move;
                if (chessBoard.IsTurn())
                {
                    if (move[4] > bestMove[4])
                    {
                        Array.Copy(move, bestMove, 5);
                    }
                }
                else
                {
                    if (move[4] < bestMove[4])
                    {
                        Array.Copy(move, bestMove, 5);
                    }
                }
            }
        }
        return bestMove;
    }
4

1 に答える 1

0

評価を間違えている。いずれかの側が独自の最良の未来を選択していることを確認する必要があります。正直なところ、移動のデータ構造を考えると、ミニマックスは簡単に実装できます。これが固定評価関数です。

    public static int[] Evaluate(List<int[]> futures, Board chessBoard)
    {
        int[] bestMove = new int[5];
        Random rndMove = new Random();
        Array.Copy(futures[rndMove.Next(futures.Count)], bestMove, 5);
        if (chessBoard.Depth() == toDepth)
        {
            if (chessBoard.IsTurn())
            {
                // Maximum
                //bestMove[4] = -1000000;
                foreach (int[] move in futures)
                {
                    if (move[4] > bestMove[4])
                    {
                        Array.Copy(move, bestMove, 5);
                    }
                }
            }
            else
            {
                // Minimum
                //bestMove[4] = 1000000;
                foreach (int[] move in futures)
                {
                    if (move[4] < bestMove[4])
                    {
                        Array.Copy(move, bestMove, 5);
                    }
                }

            }
        }
        else
        {

            if (chessBoard.IsTurn())
            {
                // Maximum
                //bestMove[4] = -1000000;
                foreach (int[] move in futures)
                {
                    if (Math.Abs(chessBoard.Pieces()[move[3], move[2]]) == king) return move;
                    Board newBoard = MoveToBoard(move, chessBoard);
                    move[4] += Evaluate(CalcFutures(newBoard), newBoard)[4];
                    if (move[4] > bestMove[4])
                    {
                        Array.Copy(move, bestMove, 5);
                    }
                }
            }
            else
            {
                // Minimum
                //bestMove[4] = 1000000;
                foreach (int[] move in futures)
                {
                    if (Math.Abs(chessBoard.Pieces()[move[3], move[2]]) == king) return move;
                    Board newBoard = MoveToBoard(move, chessBoard);
                    move[4] += Evaluate(CalcFutures(newBoard), newBoard)[4];
                    if (move[4] < bestMove[4])
                    {
                        Array.Copy(move, bestMove, 5);
                    }
                }
            }
        }
        //Console.WriteLine(bestMove[4]);
        return bestMove;
    }
于 2016-02-08T06:16:38.880 に答える