A*算法拼图游戏

Posted fuao2000

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了A*算法拼图游戏相关的知识,希望对你有一定的参考价值。

数据结构

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 拼图
{


    /// <summary>
    /// 空格移动的方向
    /// </summary>
    enum Direction
    {
        None,
        Up,
        Left,
        Right,
        Down
    }

    /// <summary>
    /// 解答
    /// </summary>
    enum Answer
    {
        /// <summary>
        /// 不存在解答
        /// </summary>
        NotExist,
        /// <summary>
        /// 存在解答
        /// </summary>
        Exist,
        /// <summary>
        /// 在当前指定的搜索深度内不存在解答(需要扩大深度)
        /// </summary>
        NotExistInDepth
    }


    /// <summary>
    /// 局面状态信息
    /// </summary>
    class StateMsg
    {
        private int depth;
        private Direction dir;
        public int Depth
        {
            get { return depth; }
        }
        public Direction Dir
        {
            get { return dir; }
        }

        public StateMsg(int depth, Direction dir)
        {
            this.depth = depth;
            this.dir = dir;
        }
    }


    /// <summary>
    /// 局面状态
    /// </summary>
    class State : StateMsg
    {
        private long code;

        /// <summary>
        /// 棋盘的编码
        /// </summary>
        public long Code
        {
            get { return code; }
            set { code = value; }
        }

        public State(long code, int depth, Direction dir)
            : base(depth, dir)
        {
            this.code = code;
        }
    }
}

Astar类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 拼图
{
    class AStar
    {

        /// <summary>
        /// ten[i]代表10的i次方
        /// </summary>
        private static readonly long[] tens = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };

        /// <summary>
        /// 不是合理的编码
        /// </summary>
        private const int OutOfCode = -1;

        /// <summary>
        /// 标志是否找到目标状态的编码
        /// </summary>
        public const int WinnerCode = 1;

        private Direction[][] dirs;

        private int startBoard;
        private static int endBoard;
        private static int[] endBoardArray;

        private int MaxDepth;

        private SortedList<long, StateMsg> openList;
        private Dictionary<long, StateMsg> boardtable;

        private const  long  maxNodes = 9223372036854775800; //至多搜索的结点数=最大局面状态数量:(9!)=362880;

        private long nodes;
        private int same;
        private float time;
        private Direction[] result;

        /// <summary>
        /// 已经访问的结点数量
        /// </summary>
        public long Nodes
        {
            get { return nodes; }
        }

        /// <summary>
        /// 重复访问相同结点的数量
        /// </summary>
        public int Same
        {
            get { return same; }
        }

        public float Time
        {
            get { return time; }
        }

        public static int N;

        /// <summary>
        /// 最终结果
        /// </summary>
        public Direction[] Result
        {
            get { return result; }
        }

        public AStar(int n)
        {
            N = n;
            dirs = new Direction[n*n][];
            if(n==3)
            {
                dirs[0] = new Direction[] { Direction.Right, Direction.Down };
                dirs[1] = new Direction[] { Direction.Left, Direction.Right, Direction.Down };
                dirs[2] = new Direction[] { Direction.Left, Direction.Down };
                dirs[3] = new Direction[] { Direction.Up, Direction.Right, Direction.Down };
                dirs[4] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
                dirs[5] = new Direction[] { Direction.Up, Direction.Left, Direction.Down };
                dirs[6] = new Direction[] { Direction.Up, Direction.Right };
                dirs[7] = new Direction[] { Direction.Left, Direction.Right, Direction.Up };
                dirs[8] = new Direction[] { Direction.Up, Direction.Left };
            }
            else
            {
                dirs[0] = new Direction[] { Direction.Right, Direction.Down };
                dirs[1] = new Direction[] { Direction.Left, Direction.Right, Direction.Down };
                dirs[2] = new Direction[] { Direction.Left, Direction.Right, Direction.Down };
                dirs[3] = new Direction[] { Direction.Left, Direction.Right, Direction.Down };
                dirs[4] = new Direction[] { Direction.Left, Direction.Down };
                dirs[5] = new Direction[] { Direction.Up, Direction.Right, Direction.Down };

                dirs[6] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
                dirs[7] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
                dirs[8] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };

                dirs[9] = new Direction[] { Direction.Up, Direction.Left, Direction.Down };
                dirs[10] = new Direction[] { Direction.Up, Direction.Right, Direction.Down };

                dirs[11] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
                dirs[12] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
                dirs[13] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };

                dirs[14] = new Direction[] { Direction.Up, Direction.Left, Direction.Down };
                dirs[15] = new Direction[] { Direction.Up, Direction.Right, Direction.Down };

                dirs[16] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
                dirs[17] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
                dirs[18] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };

                dirs[19] = new Direction[] { Direction.Up, Direction.Left, Direction.Down };
                dirs[20] = new Direction[] { Direction.Up, Direction.Right };
                dirs[21] = new Direction[] { Direction.Left, Direction.Right, Direction.Up };
                dirs[22] = new Direction[] { Direction.Left, Direction.Right, Direction.Up };
                dirs[23] = new Direction[] { Direction.Left, Direction.Right, Direction.Up };
                dirs[24] = new Direction[] { Direction.Up, Direction.Left };
            }
            

        }

        /// <summary>
        /// 求与目标位置不同的个数(不计空格,因此返回值0~8)
        /// </summary>
        /// <param name="curboard"></param>
        /// <returns></returns>
        public static int Different(int curboard)
        {
            int t_start = curboard;
            int emp_start = curboard % 10;
            int ev = 0;
            //写2个for是为了减少9个if
            for (int i = N*N; i > emp_start; i--)
            {
                t_start /= 10;
                if (t_start % 10 != endBoardArray[i])
                    ev++;
            }
            for (int i = emp_start - 1; i >= 1; i--)
            {
                t_start /= 10;
                if (t_start % 10 != endBoardArray[i])
                    ev++;
            }
            return ev;
        }

        public static int getBoard(long code)
        {
            return (int)(code % tens[9]);
        }

        private static int getEval(long code)
        {
            return (int)(code / tens[9]);
        }

        private static int getEmpIndex(long code)
        {
            return (int)(code % 10);
        }

        private static long combinCode(int board, int eval)
        {
            long codehead = eval * tens[9];
            return codehead + board;
        }

        /// <summary>
        /// 改变局面(移动空格)
        /// </summary>
        /// <param name="code"></param>
        /// <param name="dir"></param>
        public static long change(long code, Direction dir)
        {
            int newboard;
            int eval;
            int num;
            int t0;
            long t1;
            long t2;
            switch (dir)
            {
                case Direction.Left:
                    newboard = getBoard(code) - 1;
                    if (newboard == endBoard)
                        return WinnerCode;
                    eval = Different(newboard);
                    return combinCode(newboard, eval);
                case Direction.Right:
                    newboard = getBoard(code) + 1;
                    if (newboard == endBoard)
                        return WinnerCode;
                    eval = Different(newboard);
                    return combinCode(newboard, eval);
                case Direction.Up:
                    num = getBoard(code);
                    t0 = N*N - num % 10 + 1;
                    t1 = num / tens[t0];
                    t2 = t1 % 1000;
                    t1 = t1 - t2 + (t2 % 100) * 10 + t2 / 100;
                    t1 *= tens[t0];
                    newboard = (int)(t1 + ((num % tens[t0]) - N));
                    if (newboard == endBoard)
                        return WinnerCode;
                    eval = Different(newboard);
                    return combinCode(newboard, eval);
                case Direction.Down:
                    num = getBoard(code);
                    t0 = N*N - num % 10 + 1 - N;//跟Up不同的地方
                    t1 = num / tens[t0];
                    t2 = t1 % 1000;
                    t1 = t1 - t2 + (t2 % 10) * 100 + t2 / 10;//跟Up不同的地方
                    t1 *= tens[t0];
                    newboard = (int)(t1 + ((num % tens[t0]) + N));//跟Up不同的地方
                    if (newboard == endBoard)
                        return WinnerCode;
                    eval = Different(newboard);
                    return combinCode(newboard, eval);
            }
            return OutOfCode;
        }

        /// <summary>
        /// 恢复上一步的局面
        /// </summary>
        /// <param name="code"></param>
        /// <param name="dir"></param>
        public long unchange(long code, Direction dir)
        {
            return change(code, (Direction)(5 - dir));
        }

        /// <summary>
        /// 当找到目标时,从哈希表里找原来的路径
        /// </summary>
        /// <param name="endCode"></param>
        /// <param name="depth"></param>
        private void setResult(long endCode, Direction curDir, Direction lastDir, int depth)
        {
            long lastCode = endCode;
            result = new Direction[depth];
            result[depth - 1] = curDir;
            for (int i = depth - 2; i >= 0; i--)
            {
                if (boardtable.ContainsKey(lastCode))
                {
                    result[i] = boardtable[lastCode].Dir;
                    lastCode = unchange(lastCode, result[i]);
                }
                else
                    return;
            }
        }

        //本算法的核心部分
        #region 带Open表和HashTable的最好优先搜索(每次扩展Open表后都对Open表排序)

        /// <summary>
        ///  扩展Open表
        /// </summary>
        /// <param name="board"></param>
        /// <param name="depth"></param>
        private bool extentOpenList(long code, Direction lastDir, int depth)
        {
            int empIndex = (int)(code % 10 - 1);
            int len_moves = dirs[empIndex].Length;
            long newcode;
            Direction dir = 5 - lastDir;
            for (int i = 0; i < len_moves; i++)
                if (dirs[empIndex][i] != dir)
                {
                    newcode = change(code, dirs[empIndex][i]);

                    //跟目标匹配,结束
                    if (newcode == WinnerCode)
                    {
                        setResult(code, dirs[empIndex][i], lastDir, depth);
                        return true;
                    }
                    if (newcode <= tens[8])
                        throw new Exception("棋盘码修改错误! ");
                    if (newcode != OutOfCode)
                    {
                        if (!openList.ContainsKey(newcode))
                        {
                            if (!boardtable.ContainsKey(newcode))
                                openList.Add(newcode, new StateMsg(depth, dirs[empIndex][i]));
                            else
                            {
                                same++;
                                if (depth < boardtable[newcode].Depth)
                                {
                                    boardtable.Remove(newcode);
                                    openList.Add(newcode, new StateMsg(depth, dirs[empIndex][i]));
                                }
                            }
                        }
                        else
                        {
                            same++;
                            if (depth < openList[newcode].Depth)
                                openList[newcode] = new StateMsg(depth, dirs[empIndex][i]);
                        }
                    }
                }
            return false;
        }

        /// <summary>
        /// 带Open表和HashTable的最好优先搜索(每次扩展Open表后都对Open表排序)
        /// </summary>
        /// <returns></returns>
        private int BestFirstSearch()
        {
            int depth = 1;
            Direction[] moves;
            int board;
            long newCode = combinCode(this.startBoard, 0);
            int empIndex = getEmpIndex(newCode);

            moves = dirs[empIndex - 1];
            StateMsg data;
            if (extentOpenList(newCode, Direction.None, depth))
                return WinnerCode;
            while (openList.Count > 0)
            {
                lock (this)
                {
                    nodes++;
                    if (nodes >= maxNodes)
                        return -1;
                    newCode = openList.Keys[0];
                    board = getBoard(newCode);
                    data = openList[newCode];
                    if (data.Depth != 0)
                    {
                        depth = data.Depth;
                        if (board == endBoard)
                        {
                            return WinnerCode;
                        }
                        boardtable.Add(newCode, new StateMsg(depth, data.Dir));
                        openList.RemoveAt(0);
                        if (depth < MaxDepth)
                            if (extentOpenList(newCode, data.Dir, depth + 1))
                                return WinnerCode;
                    }
                }
            }
            return -1;
        }

        #endregion

        /// <summary>
        /// 把一维数组的局面编码成一个整数的表示形式
        /// </summary>
        /// <param name="genBoard"></param>
        /// <returns></returns>
        public int ToIntBoard(int[] genBoard)
        {
            int board = 0;
            int emp = 0;
            for (int i = 0; i < genBoard.Length; i++)
            {
                if (genBoard[i] != 0)
                    board = 10 * board + genBoard[i];
                else
                    emp = i + 1;
            }
            return 10 * board + emp;
        }

        /// <summary>
        /// 判断是否可以从初始状态到达目标状态(计算两个状态的逆序列,奇偶性相同的返回true)
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        private bool ExistAns(int[] start, int[] end)
        {
            int sequence_start = 0, sequence_end = 0;
            for (int i = 0; i < start.Length; i++)
            {
                if (start[i] != 0)
                    for (int j = i + 1; j < start.Length; j++)
                    {
                        if (start[j] != 0 && start[j] < start[i])
                            sequence_start++;
                    }
                if (end[i] != 0)
                    for (int j = i + 1; j < start.Length; j++)
                    {
                        if (end[j] != 0 && end[j] < end[i])
                            sequence_end++;
                    }
            }
            return (sequence_start + sequence_end) % 2 == 0;
        }

        /// <summary>
        /// 初始化求解
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="maxDepth"></param>
        private void InitComp(int[] start, int[] end, int maxDepth)
        {
            nodes = 0;
            same = 0;
            MaxDepth = maxDepth;
            result = null;
            boardtable = new Dictionary<long, StateMsg>();
            openList = new SortedList<long, StateMsg>();
            //openStack = new Stack<State>();
            //openQueue = new Queue<State>();

            this.startBoard = ToIntBoard(start);
            endBoard = ToIntBoard(end);
            int t_end = endBoard;
            int emp_end = endBoard % 10;
            endBoardArray = new int[N*N+1];
            endBoardArray[0] = emp_end;
            endBoardArray[emp_end] = 0;
            for (int i = N*N; i > emp_end; i--)
            {
                t_end /= 10;
                endBoardArray[i] = t_end % 10;
            }
            for (int i = emp_end - 1; i >= 1; i--)
            {
                t_end /= 10;
                endBoardArray[i] = t_end % 10;
            }
        }




        /// <summary>
        /// 求解8数码问题
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <param name="maxDepth"></param>
        /// <returns></returns>
        public Answer Compute(int[] start, int[] end, int maxDepth)
        {
            if (!ExistAns(start, end))
                return Answer.NotExist;
            InitComp(start, end, maxDepth);
            if (startBoard == endBoard)
                return Answer.Exist;
            long oldtime = System.DateTime.Now.Ticks;
            int eval = 0;
            eval = BestFirstSearch();
            time = (System.DateTime.Now.Ticks - oldtime) / 10000000.0f;
            if (eval == WinnerCode)
                return Answer.Exist;
            return Answer.NotExistInDepth;
        }


    }
}

 

以上是关于A*算法拼图游戏的主要内容,如果未能解决你的问题,请参考以下文章

移动迷宫——拼图游戏

移动迷宫——拼图游戏

算法 - 智能拼图AI

学习python:实例3.终端版拼图游戏

速度挑战 - 2小时完成HTML5拼图小游戏

速度挑战 - 2小时完成HTML5拼图小游戏