in Games/Fifteen/GamePage.xaml.cs [206:298]
bool SwapBlank(int row, int col)
{
//Prevent tile slides once puzzle is solved
if (DialogGrid.Visibility == Visibility.Visible || _gameOver)
{
return false;
}
if (!((((row == _blankRow - 1) || (row == _blankRow + 1)) && (col == _blankCol)) ||
(((col == _blankCol - 1) || (col == _blankCol + 1)) && (row == _blankRow))))
{
return false;
}
GazeInput.DwellFeedbackProgressBrush = new SolidColorBrush(Colors.Transparent);
_animationActive = true;
GazeInput.SetInteraction(GameGrid, Interaction.Disabled);
//Slide button visual
Button btn = _buttons[row, col];
Button blankBtn = _buttons[_blankRow, _blankCol];
//Get Visuals for the selected button that is going to appear to slide and for the blank button
var btnVisual = ElementCompositionPreview.GetElementVisual(btn);
var compositor = btnVisual.Compositor;
var blankBtnVisual = ElementCompositionPreview.GetElementVisual(blankBtn);
var easing = compositor.CreateLinearEasingFunction();
if (_slideBatchAnimation != null)
{
_slideBatchAnimation.Completed -= SlideBatchAnimation_Completed;
_slideBatchAnimation.Dispose();
}
_slideBatchAnimation = compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
_slideBatchAnimation.Completed += SlideBatchAnimation_Completed;
//Create an animation to first move the blank button with its updated contents to
//instantly appear in the position position of the selected button
//then slide that button back into its original position
var slideAnimation = compositor.CreateVector3KeyFrameAnimation();
slideAnimation.InsertKeyFrame(0f, btnVisual.Offset);
slideAnimation.InsertKeyFrame(1f, blankBtnVisual.Offset, easing);
slideAnimation.Duration = TimeSpan.FromMilliseconds(500);
//Apply the slide animation to the blank button
blankBtnVisual.StartAnimation(nameof(btnVisual.Offset), slideAnimation);
//Pulse after slide if sliding to correct position
if (((_blankRow * _boardSize) + _blankCol + 1).ToString() == btn.Content.ToString())
{
var springSpeed = 50;
blankBtnVisual.CenterPoint = new System.Numerics.Vector3((float)blankBtn.ActualWidth / 2, (float)blankBtn.ActualHeight / 2, 0f);
var scaleAnimation = compositor.CreateSpringVector3Animation();
scaleAnimation.InitialValue = new System.Numerics.Vector3(0.9f, 0.9f, 0f);
scaleAnimation.FinalValue = new System.Numerics.Vector3(1.0f, 1.0f, 0f);
scaleAnimation.DampingRatio = 0.4f;
scaleAnimation.Period = TimeSpan.FromMilliseconds(springSpeed);
scaleAnimation.DelayTime = TimeSpan.FromMilliseconds(500);
blankBtnVisual.StartAnimation(nameof(blankBtnVisual.Scale), scaleAnimation);
}
_slideBatchAnimation.End();
//Swap content of the selected button with the blank button and clear the selected button
_buttons[_blankRow, _blankCol].Content = _buttons[row, col].Content;
_buttons[row, col].Content = "";
_blankRow = row;
_blankCol = col;
//Note there is some redunancy in the following settings that corrects the UI at board load as well as tile slide
//Force selected button to the bottom and the blank button to the top
Canvas.SetZIndex(btn, -_boardSize);
Canvas.SetZIndex(blankBtn, 0);
//Update the background colors of the two buttons to reflect their new condition
btn.Background = _blankTileBrush;
blankBtn.Background = _solidTileBrush;
//Update the visibility to collapse the selected button that is now blank
btn.Visibility = Visibility.Collapsed;
blankBtn.Visibility = Visibility.Visible;
//Disable eye control for the new empty button so that there are no inappropriate dwell indicators
GazeInput.SetInteraction(blankBtn, Interaction.Inherited);
GazeInput.SetInteraction(btn, Interaction.Disabled);
return true;
}