/// <summary>
        /// Распознаёт открытый клиент игры по скриншоту.
        /// Анализирует состояние игры и делает ход при помощи мыши.
        /// </summary>
        /// <returns>Было ли состояние игры распознано как корректное</returns>
        public bool MakeMovement()
        {
            start = Stopwatch.StartNew();

            var screen = GetScreenshot();
            Logger.Log(screen);

            gameState = GetGameState(screen);
            if (gameState == null)
            {
                List<Point> closes = templatesStorage.GetPositions("Close", screen, true);
                if (closes.Count > 0)
                {
                    Mouse.Move(closes[0]);
                    Thread.Sleep(200);
                    Mouse.LeftClick();
                    Thread.Sleep(200);
                    Mouse.Move(0, 0);
                    return true;
                }
                else
                {
                    closes = templatesStorage.GetPositions("RandomPlayerGame", screen, true);
                    if (closes.Count > 0)
                    {
                        Mouse.Move(closes[0]);
                        Thread.Sleep(200);
                        Mouse.LeftClick();
                        Thread.Sleep(200);
                        Mouse.Move(0, 0);
                        Thread.Sleep(5000);
                        return true;
                    }
                }
                return false;
            }

            var Ai = new ClassicAI(gameState);
            var movement = Ai.GetGoodMovement(3);

            MakeMovement(movement);
            Logger.Log(movement);
            return true;
        }
        /// <summary>
        /// Получает все возможные состояния игры, получаемые из
        /// исходного состояния за один ход
        /// </summary>
        /// <param name="source">Исходное состояние</param>
        /// <returns>Словарь возможных состояний</returns>
        public Dictionary<Movement, ClassicGameState> GetAllMovements(ClassicGameState source)
        {
            Validate(source);
            var result = new Dictionary<Movement, ClassicGameState>();
            if (source.GameOver())
                return result;

            AddSwaps(result, source);
            if (result.Count == 0)
                return result; // некорректное состояние игры

            if (source.CurrentPlayer.Bombs > 0)
                AddExplosiveItemUsages(result, source, isBomb: true);

            if (source.CurrentPlayer.Dynamits > 0)
                AddExplosiveItemUsages(result, source, isBomb: false);

            AddEmptyMovement(result, source);

            return result;
        }
        private ClassicGameState GetGameState(Bitmap screen)
        {
            gameEntities = GetGameEntities(screen);

            var inBoardEntities = gameEntities
                .Where(pair => EnumerateInBoardEntities()
                .Contains(pair.Value))
                .ToDictionary(pair => pair.Key, pair => pair.Value);

            if (inBoardEntities.Count != 63)
            {
                return null;
            }

            leftTopBoardCell = GetLeftTopBoardCell(inBoardEntities);
            cellWidth = GetMaxDistance(inBoardEntities.Keys, p => p.X) / (BoardWidth - 1);
            cellHeight = GetMaxDistance(inBoardEntities.Keys, p => p.Y) / (BoardHeight - 1);

            var simpleBoard = GetSimpleBoard(inBoardEntities);
            var gameState = new ClassicGameState(simpleBoard);

            // Hp...
            var hps = templatesStorage.GetPositions("HpHeart", screen, true);
            if (hps.Count == 2)
            {
                var pos1 = hps[0];
                int width = templatesStorage.GetTemplate("HpHeart").Width;

                double hpLeft = 0;
                for (int i = 1; i <= 227; ++i)
                {
                    var toCheck = pos1;
                    toCheck.X += width + i;
                    if (i > 180)
                        toCheck.Y += 7;
                    var pixel = screen.GetPixel(toCheck.X, toCheck.Y);
                    if (Math.Max(pixel.R, pixel.G) > 180)
                        hpLeft++;
                }

                gameState.CurrentPlayer.Hp.Current = (int)Math.Round(hpLeft / 227 * gameState.CurrentPlayer.Hp.Max);
                if (gameState.CurrentPlayer.Hp.Current == 0)
                    gameState.CurrentPlayer.Hp.Current = 1;

                pos1 = hps[1];

                hpLeft = 0;
                for (int i = 1; i <= 227; ++i)
                {
                    var toCheck = pos1;
                    toCheck.X -= i;
                    if (i > 180)
                        toCheck.Y += 7;
                    var pixel = screen.GetPixel(toCheck.X, toCheck.Y);
                    if (Math.Max(pixel.R, pixel.G) > 180)
                        hpLeft++;
                }

                gameState.AnotherPlayer.Hp.Current = (int)Math.Round(hpLeft / 227 * gameState.AnotherPlayer.Hp.Max);

                if (gameState.AnotherPlayer.Hp.Current == 0)
                    gameState.AnotherPlayer.Hp.Current = 1;

                Console.WriteLine("Hps: {0} {1}", gameState.CurrentPlayer.Hp.Current, gameState.AnotherPlayer.Hp.Current);
            }

            /*
            gameState.CurrentPlayer.Hp.Current = 50;
            gameState.AnotherPlayer.Hp.Current = 50;

             */
            foreach (var entity in EnumerateNotInBoardEntities())
            {
                int countLeft = GetNotInBoardEntitiesCount(entity, true);
                int countRight = GetNotInBoardEntitiesCount(entity, false);
                var name = entity + "s";

                if (entity == "TurtleShell")
                {
                    if (countRight > 0)
                        gameState.CurrentPlayer.Bombs -= 10;
                }
                else
                {

                    gameState.CurrentPlayer.GetType()
                        .GetProperty(entity + "s").SetValue(gameState.CurrentPlayer, countLeft);

                    gameState.AnotherPlayer.GetType()
                        .GetProperty(entity + "s").SetValue(gameState.AnotherPlayer, countRight);
                }
            }

            return gameState;
        }
Пример #4
0
        private KeyValuePair<Movement, ClassicGameState> GetGoodMovementImpl(ClassicGameState state, 
			int depth, ref int switches, CancellationToken ct)
        {
            // Все ходы CurrentPlayer. В этих состояниях CurrentPlayer == Enemy
            var map = state.Rules.GetAllMovements(state);

            if (map.Count == 0)
                return new KeyValuePair<Movement, ClassicGameState>(null, state);

            var result = new KeyValuePair<Movement,ClassicGameState>();

            if (depth == maxRecursionDepth)
            {
                // Наихудший ход для Enemy
                GetBestState(map, -1, ref result);
            }
            else
            {
                var resultArray = new KeyValuePair<Movement, ClassicGameState>[map.Count];

                // Тут храним количество смен игроков. Так как игра может закончится
                // внезапно, формула, использующая maxRecursionDepth не годится
                var resultSwitches = new int[map.Count];

                ct.ThrowIfCancellationRequested();

                if (depth == 1) // запускаем параллельный алгоритм
                {
                    Parallel.ForEach(map, (pair, loopState, iteration) =>
                        {
                            resultArray[iteration] = new KeyValuePair<Movement,ClassicGameState>(
                                pair.Key, GetGoodMovementImpl(pair.Value, depth + 1, ref resultSwitches[iteration], ct).Value);
                        });
                }
                else
                {
                    int index = 0;
                    foreach (var pair in map)
                    {
                        resultArray[index] = new KeyValuePair<Movement,ClassicGameState>(pair.Key,
                            GetGoodMovementImpl(pair.Value, depth + 1, ref resultSwitches[index], ct).Value);
                        ++index;
                    }
                }

                var max = double.NegativeInfinity;
                int minSwitches = int.MaxValue;
                for (int i = 0; i < resultArray.Length; ++i)
                {
                    int mult = resultSwitches[i] % 2 == 0 ? -1 : 1;
                    var currHpRatio = GetCurrentHpRatio(resultArray[i].Value) * mult;
                    if (currHpRatio > max || currHpRatio > 10000 &&
                        resultSwitches[i] < minSwitches)
                    {
                        minSwitches = resultSwitches[i];
                        switches = resultSwitches[i];
                        max = mult * GetCurrentHpRatio(resultArray[i].Value);
                        result = resultArray[i];
                    }
                }
            }

            ++switches;
            return result;
        }
Пример #5
0
 /// <summary>
 /// Соотносит текущие жизни игроков в игре state
 /// </summary>
 double GetCurrentHpRatio(ClassicGameState state)
 {
     return GetHpRatio(state.CurrentPlayer, state.AnotherPlayer);
 }
Пример #6
0
 public ClassicAI(ClassicGameState state)
 {
     this.state = state;
 }
 private static void ChangeGameState(ClassicGameState state, double heal, double damage, int bombs, int dynamits, int diamonds)
 {
     state.CurrentPlayer.Hp.AddHealth((int)Math.Round(heal));
     state.AnotherPlayer.Hp.AddHealth(-(int)Math.Round(damage));
     state.CurrentPlayer.Bombs += bombs;
     state.CurrentPlayer.Dynamits += dynamits;
 }
 private void Validate(ClassicGameState source)
 {
     if (source.Board.Width != _width || source.Board.Height != _height)
         throw new ArgumentException("Incorrect source size");
 }
        private ClassicGameState GetStateAfterExplosition(Point[] explositionShape, ClassicGameState state, bool isBomb)
        {
            double heal;
            double damage;
            int addBombs = isBomb ? -1 : 0;
            int addDynamits = isBomb ? 0 : -1;
            int addDiamonds;

            var result = state.Clone();

            ExplodeOnce(explositionShape, result.Board, out heal, out damage, out addDiamonds);

            // TODO: вынести информацию о доп. уроне в класс Player
            damage += isBomb ? +2 : +3;

            addBombs = Math.Min(addBombs, 1);
            addDynamits = Math.Min(addDynamits, 1);

            ChangeGameState(result, heal, damage, addBombs, addDynamits, addDiamonds);
            ExplodeSecondariesOnly(result);

            return result;
        }
        /// <summary>
        /// Получает новое состояние игры при помощи обмена элементов и последующими взрывами
        /// </summary>
        private ClassicGameState GetStateAfterSwap(Point first, Point second, ClassicGameState source)
        {
            Swap(first, second, source.Board);
            var result = Explode(source);
            Swap(first, second, source.Board);

            return result;
        }
        /// <summary>
        /// Изменяет состояние игры, производя только вторичные взрывы в соответствии с правилами игры
        /// </summary>
        /// <param name="result"></param>
        private void ExplodeSecondariesOnly(ClassicGameState result)
        {
            double heal = 0;
            double damage = 0;
            int bombs = 0;
            int dynamits = 0;
            int diamonds = 0;

            while (IsDetonationRequired(result.Board))
            {
                double newHeal;
                double newDamage;
                int newBombs;
                int newDynamits;

                ExplodeOnce(result.Board, out newHeal, out newDamage, out newBombs, out newDynamits, out diamonds);

                heal += newHeal * _secondaryBouncesMultiplier;
                damage += newDamage * _secondaryBouncesMultiplier;
                bombs += newBombs;
                dynamits += newDynamits;
            }

            ChangeGameState(result, heal, damage, bombs, dynamits, diamonds);
            result.SwapPlayers();
        }
        /// <summary>
        /// Получает новое состояние игры, путём взрывов элементов в соответствии с правилами.
        /// Производит все серии взрывов. Смена хода также произодится.
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        private ClassicGameState Explode(ClassicGameState source)
        {
            double heal;
            double damage;
            int bombs;
            int dynamits;
            int diamonds;

            var result = source.Clone();

            // Первая серия взрывов
            ExplodeOnce(result.Board, out heal, out damage, out bombs, out dynamits, out diamonds);
            ChangeGameState(result, heal, damage, bombs, dynamits, diamonds);

            ExplodeSecondariesOnly(result);

            return result;
        }
        /// <summary>
        /// Добавляет в словарь ходов, все возможные ходы с обменом элементов
        /// </summary>
        /// <param name="dest"></param>
        /// <param name="state"></param>
        private void AddSwaps(Dictionary<Movement, ClassicGameState> dest, ClassicGameState state)
        {
            foreach (var movement in GetUncheckedSwaps(state.Board))
            {
                var classicMovement = (ClassicMovement)movement;
                Point first = classicMovement.First;
                Point second = classicMovement.Second;

                if (IsSwapCorrect(first, second, state.Board))
                {
                    var newMovement = new ClassicMovement { First = first, Second = second };
                    dest.Add(movement, GetStateAfterSwap(first, second, state));
                }
            }
        }
        /// <summary>
        /// Добавляет в словарь ходов, все возможные ходы с использованием взрывчатки
        /// </summary>
        private void AddExplosiveItemUsages(Dictionary<Movement, ClassicGameState> dest, ClassicGameState state,
			bool isBomb)
        {
            var current = isBomb ? ClassicMovementKind.Bomb : ClassicMovementKind.Dynamit;
            var explosiveItemGetShape = isBomb ? (Func<Point, Point[]>)GetBombShape : GetDynamitShape;

            for (int x = 1; x <= state.Board.Width; ++x)
                for (int y = 1; y <= state.Board.Height; ++y)
                {
                    Point center = new Point(x, y);
                    var shape = explosiveItemGetShape(center);
                    ClassicMovement movement = new ClassicMovement
                        {
                            First = center,
                            Kind =  current
                        };
                    dest.Add(movement, GetStateAfterExplosition(shape, state, current == ClassicMovementKind.Bomb));
                }
        }
        private void AddEmptyMovement(Dictionary<Movement, ClassicGameState> result, ClassicGameState source)
        {
            var movement = new ClassicMovement
                {
                    Kind = ClassicMovementKind.Empty
                };

            var state = source.Clone();
            state.SwapPlayers();

            result.Add(movement, state);
        }