Пример #1
0
        void CreateNumberTile(int height, int width, int value)
        {
            NumericRect rect = new NumericRect();

            UpdateVisuals(rect, value);

            this.grid.Children.Add(rect);
            Grid.SetRow(rect, height);
            Grid.SetColumn(rect, width);
        }
Пример #2
0
        void UpdateVisuals(NumericRect tile, int number)
        {
            int valueToShow = (int)Math.Pow(2, number);

            tile.Text = valueToShow.ToString();

            Tuple <Color, Color> selectedTuple;

            if (number > 0 && number <= this.theme.BackForeColorTuples.Count)
            {
                selectedTuple = this.theme.BackForeColorTuples[number - 1];
            }
            else
            {
                // Если соответствия номеру в базе не нашлось, то возвращаем первый элемент
                selectedTuple = this.theme.BackForeColorTuples[0];
            }

            tile.Background = new SolidColorBrush(selectedTuple.Item1);
            tile.Foreground = new SolidColorBrush(selectedTuple.Item2);

            // Определяем, нужен ли эффект блюра вокруг квадрата
            int lowerBlurBound = 7;
            int upperBlurBound = 11;

            if (number >= lowerBlurBound && number <= upperBlurBound)
            {
                // Вычисляем возможное число состояний, т.к. если границы 7 и 11, то состояний будет 5, а не 4
                int possibleStateCount = upperBlurBound - lowerBlurBound + 1;
                // Вычисляем номер текущего состояния. Если число равно 7, то его номер 1, т.к. иначе мы получим прозрачность 0
                int currentStateNum = number - lowerBlurBound + 1;

                double opacity = ((double)currentStateNum / possibleStateCount) * 1; // 1 - максимальная прозрачность

                System.Windows.Media.Effects.DropShadowEffect shadow =
                    new System.Windows.Media.Effects.DropShadowEffect();

                shadow.Opacity     = opacity;
                shadow.Color       = this.theme.BackForeColorTuples[number - 1].Item1; //selectedTuple.Item1;
                shadow.BlurRadius  = 15;
                shadow.ShadowDepth = 0;

                tile.Effect = shadow;
            }
        }
Пример #3
0
        void UpdateResources()
        {
            if (cfg == null)
            {
                return;
            }

            double numRectRatio  = 0.85;
            double gridLineRatio = 1 - numRectRatio;

            var    gridSize  = grid.RenderSize;
            double rowLength = gridSize.Height / cfg.Height;
            double colLength = gridSize.Width / cfg.Width;

            double least = rowLength > colLength ? colLength : rowLength;
            double singleLineThickness = least * gridLineRatio / 2;

            double numRectHeight;
            double numRectWidth;

            if (rowLength > colLength)
            {
                // если растянуто больше вертикально
                numRectWidth  = colLength * numRectRatio;
                numRectHeight = rowLength - 2 * singleLineThickness;
            }
            else
            {
                // если сильно растянуто горизонтально
                numRectHeight = rowLength * numRectRatio;
                numRectWidth  = colLength - 2 * singleLineThickness;
            }

            this.Resources["GridThickness"]     = singleLineThickness;
            this.Resources["NumericRectHeight"] = numRectHeight;
            this.Resources["NumericRectWidth"]  = numRectWidth;

            double cornerRadius = NumericRect.CalculateCornerRadius(numRectHeight, numRectWidth);

            this.Resources["CornerRadius"] = cornerRadius;
        }
Пример #4
0
        void UpdateGame()
        {
            NumericRect[] rects = grid.Children.OfType <NumericRect>().ToArray();

            // обновляем информацию о числе попыток
            //this.rollbackTextblock.Text = $"Попыток: {this.game.RollbackCount}";
            this.undoInfoBlock.UpperText = $"{this.game.RollbackCount} left";
            // обновляем счет
            this.scoreInfoBlock.LowerText = $"{CalculateScore(this.game.MergeMap)}";

            // Удаляем все цифровые блоки
            foreach (NumericRect rect in rects)
            {
                this.grid.Children.Remove(rect);
            }

            // Удаляем экран GameOver, если такой есть
            Border gameOverBorder = grid.Children.OfType <Border>().FirstOrDefault();

            if (gameOverBorder != null)
            {
                this.grid.Children.Remove(gameOverBorder);
            }

            Viewbox gameOverText = grid.Children.OfType <Viewbox>().FirstOrDefault();

            if (gameOverText != null)
            {
                this.grid.Children.Remove(gameOverText);
            }


            for (int i = 0; i < this.game.Height; i++)
            {
                for (int j = 0; j < this.game.Width; j++)
                {
                    int value = this.game.Matrix[i, j];

                    // Находим прямоугольник, отвечающий за задний фон
                    //Rectangle backgroundRect = this.grid.Children.OfType<Rectangle>().First(
                    //        r => Grid.GetRow(r) == i && Grid.GetColumn(r) == j);

                    if (value > 0)
                    {
                        // Скрываем его, если в этой ячейке есть значение
                        //backgroundRect.Visibility = Visibility.Collapsed;

                        NumericRect rect = new NumericRect();
                        //rect.Text = value.ToString();
                        // задаем визуальную составляющую плитки
                        UpdateVisuals(rect, value);

                        grid.Children.Add(rect);

                        Grid.SetRow(rect, i);
                        Grid.SetColumn(rect, j);
                    }
                    else
                    {
                        // Если значения нет, то делаем прямоугольник, отвечающий за задний фон видимым
                        //backgroundRect.Visibility = Visibility.Visible;
                    }
                }
            }
        }
Пример #5
0
        Task AnimateMotionAsync(IEnumerable <Tuple <Point, Point> > sourceDestinationPoints, IEnumerable <Point> mergerPoints)
        {
            return(Task.Factory.StartNew(() =>
            {
                //Переменная, отвечающая подсчет завершившихся анимаций
                int finishedAnimationCount = 0;
                // Необходимое число анимаций
                int targetAnimationCount = sourceDestinationPoints.Count();
                // Вычисляемый флаг для получения информации о том, завершены ли все анимации

                // Узнаем высоту и ширину одной ячейки сетки ячейки
                double cellHeight = this.grid.ActualHeight / this.grid.RowDefinitions.Count;
                double cellWidth = this.grid.ActualWidth / this.grid.ColumnDefinitions.Count;

                // Как только задача будет запущена - из главного потока настроим и запустим все анимации
                this.grid.Dispatcher.Invoke(() =>
                {
                    // Временная шкала для всех анимаций
                    Storyboard storyboard = new Storyboard();

                    // Получим все элементы грида нужного типа
                    IEnumerable <NumericRect> visibleRects = this.grid.Children.OfType <NumericRect>();

                    foreach (NumericRect visibleRect in visibleRects)
                    {
                        // Определим для текущего квадрата его строку и колонку в таблице
                        int rowNum = Grid.GetRow(visibleRect);
                        int colNum = Grid.GetColumn(visibleRect);

                        // Найдем кортеж в котором первый элемент - координаты откуда надо переместить квадрат
                        // Item1 = откуда надо переместить, т.е. исходные координаты квадрата на поле
                        Tuple <Point, Point> targetTuple = sourceDestinationPoints.FirstOrDefault(
                            tuple => (int)tuple.Item1.X == colNum && (int)tuple.Item1.Y == rowNum);

                        // Если текущий квадрат не входит в число тех, которые надо переместить, т.е. нужный кортеж не найден
                        if (targetTuple == null)
                        {
                            continue;
                        }

                        // Выделяем из кортежа информацию откуда и куда надо переместить квадрат
                        Point pFrom = targetTuple.Item1;
                        Point pTo = targetTuple.Item2;

                        // Вычисляем отступ
                        double vMargin = (cellHeight - visibleRect.ActualHeight) / 2;
                        double hMargin = (cellWidth - visibleRect.ActualWidth) / 2;

                        // В эти переменные запишутся вычисленные стартовые и конечные значения отступов
                        Thickness marginFrom;
                        Thickness marginTo;

                        // Если двиг произошел вдоль строки, т.е. изменилось значение X
                        if (pFrom.X != pTo.X)
                        {
                            int delta = (int)Math.Abs(pTo.X - pFrom.X);         // Вычислим разницу между началом и концом
                            double highestMargin = hMargin + delta * cellWidth; // Определяем самый дальний отступ (направляющий)
                            Grid.SetColumnSpan(visibleRect, delta + 1);         // Зададим допустимое значение ячеек, которое может занимать квадрат

                            if (pFrom.X < pTo.X)                                // движение вправо
                            {
                                marginFrom = new Thickness(hMargin, vMargin, highestMargin, vMargin);
                                marginTo = new Thickness(highestMargin, vMargin, hMargin, vMargin);
                            }
                            else
                            {
                                Grid.SetColumn(visibleRect, (int)pTo.X); // Зададим квадрату самую левую колонку, чтобы анимация отработала правильно
                                marginFrom = new Thickness(highestMargin, vMargin, hMargin, vMargin);
                                marginTo = new Thickness(hMargin, vMargin, highestMargin, vMargin);
                            }
                        }
                        else
                        {
                            // Иначе вдоль колонки
                            int delta = (int)Math.Abs(pTo.Y - pFrom.Y);
                            double highestMargin = vMargin + delta * cellHeight;
                            Grid.SetRowSpan(visibleRect, delta + 1);

                            if (pFrom.Y < pTo.Y) // движение вниз
                            {
                                marginFrom = new Thickness(hMargin, vMargin, hMargin, highestMargin);
                                marginTo = new Thickness(hMargin, highestMargin, hMargin, vMargin);
                            }
                            else
                            {
                                Grid.SetRow(visibleRect, (int)pTo.Y); // Изменим значение строки на самое верхнее, т.к. анимация идет снизу вверх
                                marginFrom = new Thickness(hMargin, highestMargin, hMargin, vMargin);
                                marginTo = new Thickness(hMargin, vMargin, hMargin, highestMargin);
                            }
                        }

                        ThicknessAnimation thicknessAnimation = new ThicknessAnimation(marginFrom, marginTo, TimeSpan.FromSeconds(0.15));
                        // По умолчанию стоит HoldEnd, которое не дает нам изменить вручную в дальнейшем, не подходит
                        thicknessAnimation.FillBehavior = FillBehavior.Stop;

                        thicknessAnimation.Completed += (_, __) =>
                        {
                            // вернем обратно допустимое число ячеек, которое может занимать элемент (1)
                            //if (pFrom.X != pTo.X)
                            Grid.SetColumnSpan(visibleRect, 1); // если движение было по строке - восстановим макс. занимаемое число колонок
                            //else
                            Grid.SetRowSpan(visibleRect, 1);    // иначе строк

                            // и зададим верные конечные координаты
                            Grid.SetRow(visibleRect, (int)pTo.Y);
                            Grid.SetColumn(visibleRect, (int)pTo.X);

                            // Исправим отступы на нулевые для сброса значения, установленного анимацией
                            visibleRect.Margin = new Thickness(0);

                            finishedAnimationCount++; // учтем, что текущая анимация завершена
                        };

                        Storyboard.SetTarget(thicknessAnimation, visibleRect);
                        Storyboard.SetTargetProperty(thicknessAnimation, new PropertyPath(NumericRect.MarginProperty));
                        storyboard.Children.Add(thicknessAnimation);
                        //visibleRect.BeginAnimation(FrameworkElement.MarginProperty, null);
                        //visibleRect.BeginAnimation(FrameworkElement.MarginProperty, thicknessAnimation);
                    }

                    // Не блокируем значения по завершению анимаций
                    storyboard.FillBehavior = FillBehavior.Stop;
                    // Запустим анимации перемещения
                    storyboard.Begin();
                });

                // После запуска анимаций ждём пока все анимации не выполнятся,
                // сравнивая переменные из потока нашей задачи

                while (finishedAnimationCount != targetAnimationCount)
                {
                    Thread.Sleep(5);
                }

                // После окончания анимации передвижения запустим анимации слияния так же из главного потока
                // А так же обновим текущий счет
                this.grid.Dispatcher.Invoke(() =>
                {
                    if (mergerPoints.Count() == 0)
                    {
                        return;
                    }

                    // Таймлайн анимаций слияния
                    Storyboard mergerStoryboard = new Storyboard();
                    // Сбрасываем счетчик анимаций и заново устанавливаем необходимое их число
                    finishedAnimationCount = 0;
                    targetAnimationCount = mergerPoints.Count() * 2; // Для одного слияния у нас 2 анимации

                    // Обновим добавляемый счет как разницу между новым и записанным в интерфейсе
                    int newScore = CalculateScore(this.game.MergeMap);
                    int prevScore = int.Parse(this.scoreInfoBlock.LowerText);
                    int appendedScore = newScore - prevScore;
                    this.scoreInfoBlock.LowerText = $"{newScore}";

                    double expandedValueKeyTime = 0.08;
                    double defaultValueKeyTime = 0.16;

                    foreach (Point mergerPoint in mergerPoints)
                    {
                        int height = (int)mergerPoint.Y;
                        int width = (int)mergerPoint.X;

                        // Находим все квадраты, которые находятся по нужным координатам
                        NumericRect[] mergedRects = this.grid.Children.OfType <NumericRect>()
                                                    .Where(r => Grid.GetRow(r) == height && Grid.GetColumn(r) == width).ToArray();

                        if (mergedRects.Length != 2)
                        {
                            throw new InvalidDataException("В координатах слияния не находится 2 квадрата");
                        }

                        // Первый просто удаляем
                        this.grid.Children.Remove(mergedRects[0]);

                        // Выделим для удобства анимируемый квадрат
                        NumericRect animatedRect = mergedRects[1];

                        // Второй преобразуем согласно новому значению
                        UpdateVisuals(animatedRect, this.game.Matrix[height, width]);

                        // Зададим стандартные и расширенные значения для анимаций высоты и ширины
                        double defaultHeight = animatedRect.Height;
                        double expadedHeight = cellHeight;

                        double defaultWidth = animatedRect.Width;
                        double expandedWidth = cellWidth;

                        // Настраиваем анимацию высоты так, чтобы половину времени она
                        // увеличивалась и половину возвращалась до стандартного значения
                        DoubleAnimationUsingKeyFrames heightAnim = new DoubleAnimationUsingKeyFrames();
                        heightAnim.Duration = TimeSpan.FromSeconds(defaultValueKeyTime);
                        heightAnim.KeyFrames.Add(
                            new LinearDoubleKeyFrame(expadedHeight, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(expandedValueKeyTime))));
                        heightAnim.KeyFrames.Add(
                            new LinearDoubleKeyFrame(defaultHeight, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(defaultValueKeyTime))));
                        heightAnim.FillBehavior = FillBehavior.Stop;

                        Storyboard.SetTarget(heightAnim, animatedRect);
                        Storyboard.SetTargetProperty(heightAnim, new PropertyPath(NumericRect.HeightProperty));

                        // Так же настраиваем анимацию ширины
                        DoubleAnimationUsingKeyFrames widthAnim = new DoubleAnimationUsingKeyFrames();
                        widthAnim.Duration = TimeSpan.FromSeconds(defaultValueKeyTime);
                        widthAnim.KeyFrames.Add(
                            new LinearDoubleKeyFrame(expandedWidth, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(expandedValueKeyTime))));
                        widthAnim.KeyFrames.Add(
                            new LinearDoubleKeyFrame(defaultWidth, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(defaultValueKeyTime))));
                        widthAnim.FillBehavior = FillBehavior.Stop;

                        Storyboard.SetTarget(widthAnim, animatedRect);
                        Storyboard.SetTargetProperty(widthAnim, new PropertyPath(NumericRect.WidthProperty));

                        // Добавляем обе анимации на таймлайн
                        mergerStoryboard.Children.Add(heightAnim);
                        mergerStoryboard.Children.Add(widthAnim);

                        // Учет завершившихся анимаций
                        heightAnim.Completed += (_, __) =>
                        {
                            finishedAnimationCount++;
                        };

                        widthAnim.Completed += (_, __) =>
                        {
                            finishedAnimationCount++;
                        };
                    }

                    // После всех вычислений начинаем процесс анимации
                    mergerStoryboard.Begin();
                });

                while (finishedAnimationCount != targetAnimationCount)
                {
                    Thread.Sleep(5);
                }
            }));
        }