FloTiles:创新的 Metro 游戏





5.00/5 (11投票s)
一款展示创新拼图游戏的Windows 8 Metro应用,同时利用了Ultrabook的强大功能。
目录
引言
FloTiles是一款创新的拼图游戏,它引入了一些自己的规则,使游戏独具特色且更加刺激。
更新
在过去的几周里,我忙于将这款应用从概念打造成一款实用的Windows应用商店应用。我在11月20日凌晨完成了应用的第一版,并将其上传到了Windows应用商店。我最初认为11月21日是应用上传和通过微软认证的最后期限。为了赶上这个期限,我不得不删减了一些功能。后来我才发现,评审员将在12月1日检查Windows应用商店里的应用。这给了我几天时间来重新添加之前删减的功能,并且也有充足的时间来打磨我的应用。于是,我将所有被删减的功能重新摆上了桌面,开始了我的工作。11月28日,我上传了包含所有计划功能的第一个修订版应用。微软的出色表现令我惊喜,仅用了大约10个小时就完成了认证和批准。这给了我动力去计划一次快速的更新,以进一步打磨我的应用。第二个修订版于11月29日凌晨上传,并已通过了认证流程,现在可以在Windows应用商店中找到。从那时起,我一直专注于完成我的文章。
注意
我不会在这篇文章中上传全部代码。取而代之的是,我将发布该应用关键组件的代码片段。
背景
FloTiles的灵感来源于游戏FlipSaw,这是我的第一款Windows 8 Metro应用。在FlipSaw中,玩家同时玩两个拼图游戏,而在FloTiles中,玩家玩一个拼图游戏。FloTiles的独特之处在于用户必须以特定的方式排列拼图块才能获得最终的图像。
FloTiles的另一个核心组件是经过更新的FluidWrapPanel。 FluidWrapPanel是我WPFSpark项目的一部分。这个面板负责排列拼图块并处理用户交互。更多细节将在后续章节中介绍。
FloTiles解析
概念
拼图游戏的基本概念是:一张图片(或一串数字)被分割成若干个小块,形成一个m x n的网格,然后将这些块打乱,并移除其中一个块,以便通过一次移动一个块的方式,最终重建原始图像。只有与空白区域相邻的块才能移动(水平或垂直)。
FloTiles在此基础上进行了扩展。在FloTiles中,图像(或数字序列)也被分割成若干个小块,形成一个m x n的网格并被打乱。与拼图游戏的相似之处仅限于此。没有移除任何块,所以没有空白区域来移动相邻的块。相反,用户可以从网格中的任何位置拖动一个块,并将其放置在任何其他位置。其他块也会自动重新排列以适应新位置中的块。现在,这可能看起来玩家获得了很大的自由度,游戏也变得更容易了。相信我,事实并非如此。
让游戏保持有趣的关键在于拼图块的排列方式,我称之为FloPattern。
FloPattern
FloPattern描述了游戏棋盘上拼图块的排列顺序。让我举个例子。假设有5个人(A、B、C、D和E)按顺序排成一队(顺序为A, B, C, D, E)。每个人的索引就是该人在队列中的位置。在这种情况下,索引是:A - 1, B - 2, C - 3, D - 4, E - 5。
现在假设B离开队列。那么B就没有有效的索引了。但是B占据的位置并没有被其他人使用。所以下一个索引较高的人(C)会移动到B的位置,他的索引会调整为B原来的索引。然后D会移动到C的位置。这个过程一直持续到队列末尾。所以现在新的索引是:A- 1, C - 2, D - 3, E - 4。
现在B又回到了队列中,并占据了C后面的位置。所以B取走了D的索引。为了让B进入队列,所有在B之后的人都会向下移动一个索引。所以现在索引是:A - 1, C - 2, B - 3, D - 4, E - 5。
想象一下,如果人们排成一个圆圈或之字形,但仍然遵循队列系统。如果你将人们替换成构成更大图像的拼图块,你就得到了这个游戏概念的核心。
因此,FloPattern
定义了游戏棋盘内索引的顺序。这些索引最终定义了拼图块的排列顺序。每个拼图块的索引被称为FloIndex。
FloTiles工作流程
每个FloPattern
代表一个Challenge
。目前游戏中定义了10个挑战,它们是:
- 直线挑战 - I
- 直线挑战 - II
- 直线挑战 - III
- 直线挑战 - IV
- 之字形挑战 - I
- 之字形挑战 - II
- 之字形挑战 - III
- 之字形挑战 - IV
- 螺旋挑战 - I
- 螺旋挑战 - II
每个Challenge
包含9个Levels
,代表特定大小的游戏棋盘。在这些Levels
中:
- 每个
Level
的第一个子关卡将是一个5x5大小的试玩Level
。此外,拼图块不会包含图像片段。相反,它们将包含从1到25的数字。用户可以玩这个Level
来感受每当用户移动一个拼图块时FloPattern
的行为方式。这也有助于用户在后续Levels
中制定解决谜题的策略。然而,用户玩这个Level
是可选的。 - 接下来的8个
Levels
将包含3x3到10x10的游戏棋盘大小。
最初,所有挑战和Levels
(除练习Level
和直线挑战 - I的第一个Level外
)都将被锁定。成功完成一个Level
将解锁该Challenge
中的下一个Level。
成功完成任何挑战的前五个Levels将解锁下一个Challenge。最后三个Levels
(大小为8x8、9x9和10x10)的复杂度很高,可能需要用户相当长的时间和耐心。因此,用户可以选择不完成它们,直接进入下一个Challenge。然而,用户也可以在进入下一个
Challenge之前先完成最后三个
Levels。
游戏过程中,用户可以在屏幕底部向上滑动调出的底部应用栏中看到4个选项,它们是:
Level。
Level。
Level所使用的FloPattern,即游戏棋盘上拼图块的排列顺序。使用此功能将自动增加30秒的总时长。
除了上述挑战之外,用户还可以使用用户机器上的图片或通过摄像头拍照来创建自定义的FloTile挑战。只有用户已完成或正在玩的挑战才能用于创建自定义的FloTileChallenge。其余的挑战将被锁定。一旦用户通过玩主菜单中的
Challenge
的Levels
解锁了它们,它们就会被解锁。
在玩每个Level
时,将显示花费的时间和移动的次数。每个Level
完成后,这些统计数据将被保存。这些统计数据将在记分板中显示。它将显示完成Level
所花费的移动次数和时长,以及该Level
的星级评分。如果用户尚未玩过某个Level
,则会显示锁定图标。
奖励系统
每个Level
的完成都将根据一个算法进行评分,该算法将考虑移动次数、花费的时间(解决Level
的时间)和游戏棋盘的大小。根据算法的输出,用户将获得积分和该Level的1到3星评级。
FloTiles模块
Common
Common模块包含应用中使用的通用定义、枚举、常量、结构等。以下是其中一些:
PatternType
此枚举定义了可用的各种FloPatterns。
public enum PatternType : int
{
None = 0,
StraightTopLeft = 1,
StraightTopRight = 2,
StraightBottomRight = 3,
StraightBottomLeft = 4,
ZigZagTopLeft = 5,
ZigZagTopRight = 6,
ZigZagBottomRight = 7,
ZigZagBottomLeft = 8,
SpiralIn = 9,
SpiralOut = 10,
}
GameLevelType
此枚举定义了各种可用的Levels
。
public enum GameLevelType : int
{
None = 0,
Level0 = 1,
Level1 = 2,
Level2 = 3,
Level3 = 4,
Level4 = 5,
Level5 = 6,
Level6 = 7,
Level7 = 8,
Level8 = 9
}
LevelStatusType
此枚举定义了Challenge
中Level
的各种状态。
public enum LevelStatusType : int
{
None = 0,
Locked = 1,
Playing = 2,
OneStar = 3,
TwoStars = 4,
ThreeStars = 5
}
GameStatusType
此枚举定义了Level
中游戏的各种状态。
public enum GameStatusType : int
{
None = 0,
Initializing = 1,
Loading = 2,
TapToPlay = 3,
Playing = 4,
Paused = 5,
Solved = 6,
GameOver = 7,
PracticeGameOver = 8,
CustomGameOver = 9
}
GameOverType
此枚举定义了Level
的各种GameOver状态。
public enum GameOverType : int
{
None = 0,
LevelComplete = 1,
NextChallengeUnlocked = 2,
LevelCompletePostNCUnlock = 3,
ChallengeComplete = 4
}
Cell
此类定义了用于标识GameBoard中拼图块位置的结构。
public struct Cell
{
public int Row { get; set; }
public int Column { get; set; }
public static bool operator ==(Cell a, Cell b)
{
return ((a.Row == b.Row) && (a.Column == b.Column));
}
public static bool operator !=(Cell a, Cell b)
{
return ((a.Row != b.Row) || (a.Column != b.Column));
}
public override bool Equals(object obj)
{
try
{
if (obj is Cell)
{
return this == (Cell)obj;
}
}
catch (Exception)
{
}
return false;
}
public override int GetHashCode()
{
// Multiply the row by a prime (101) and add the column value to it
return (this.Row * 101) + this.Column;
}
}
Cortex
此模块包含FloTiles
的核心类,这些类用于根据所选的Challenge
和Level
创建游戏。
GamePanel
GamePanel
类代表FloTile游戏的游戏棋盘。此组件用于托管GameTiles
。它允许用户将拼图块从一个位置拖放到另一个位置。它是FluidWrapPanel的更新版本,而FluidWrapPanel是我WPFSpark项目的一部分。当前代码集成了WinRT框架中提供的异步模型。
public class GamePanel : Panel
{
#region Constants
private const double NORMAL_SCALE = 1.0d;
private const double DRAG_SCALE_DEFAULT = 1.3d;
private const double NORMAL_OPACITY = 1.0d;
private const double DRAG_OPACITY_DEFAULT = 0.6d;
private const double OPACITY_MIN = 0.1d;
private const Int32 Z_INDEX_NORMAL = 0;
private const Int32 Z_INDEX_INTERMEDIATE = 1;
private const Int32 Z_INDEX_DRAG = 10;
private static TimeSpan DEFAULT_ANIMATION_TIME_WITHOUT_EASING = TimeSpan.FromMilliseconds(200);
private static TimeSpan DEFAULT_ANIMATION_TIME_WITH_EASING = TimeSpan.FromMilliseconds(300);
private static TimeSpan FIRST_TIME_ANIMATION_DURATION = TimeSpan.FromMilliseconds(220);
#endregion
#region Delegates and Events
public event EventHandler InitializationCompleted;
public event EventHandler TileMoved;
public event EventHandler GameOver;
#endregion
#region Fields
Point _dragStartPoint = new Point();
UIElement _dragTile = null;
UIElement _lastDragElement = null;
List<UIElement> _boardTiles = null;
List<UIElement> _intermediateTiles = null;
GamePanelHelper _gamePanelHelper = null;
double _itemWidth = 0.0;
double _itemHeight = 0.0;
double _animDelay = 0.0;
int _dragStartIndex = -1;
int _dragEndIndex = -1;
#endregion
#region Dependency Properties
...
#endregion
#region Overrides
/// <summary>
/// Override for the Measure Layout Phase
/// </summary>
/// <param name="availableSize">Available Size</param>
/// <returns>Size required by the panel</returns>
protected override Size MeasureOverride(Size availableSize)
{
Size availableItemSize = new Size(Double.PositiveInfinity, Double.PositiveInfinity);
foreach (UIElement child in Children.Where(c => c != null))
{
// Ask the child how much size it needs
child.Measure(availableItemSize);
}
if ((MaxRows > 0) && (MaxCols > 0))
{
Size resultSize = new Size(MaxCols * _itemWidth, MaxRows * _itemHeight);
return resultSize;
}
return availableSize;
}
/// <summary>
/// Override for the Arrange Layout Phase
/// </summary>
/// <param name="finalSize">Available size provided by the FluidGameBoard</param>
/// <returns>Size taken up by the Panel</returns>
protected override Size ArrangeOverride(Size finalSize)
{
UpdateFluidLayout();
return finalSize;
}
#endregion
#region Construction / Initialization
public GamePanel()
{
_gamePanelHelper = new GamePanelHelper();
_boardTiles = new List<UIElement>();
_intermediateTiles = new List<UIElement>();
}
#endregion
#region APIs
async internal Task InitializeAsync(StorageFile imageFile, PatternType pattern, int maxRows, int maxCols, bool isPractice, bool isRestoring, SerializedGameTile[] serData)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
if (_boardTiles == null)
_boardTiles = new List<UIElement>();
// Clear up the previous tiles
CleanUp();
Pattern = pattern;
MaxRows = maxRows;
MaxCols = maxCols;
// Initialize the _gamePanelHelper
_itemWidth = this.Width / (double)MaxCols;
_itemHeight = this.Height / (double)MaxRows;
_gamePanelHelper.Initialize(this.Dispatcher, this.Width, this.Height, MaxRows, MaxCols, _itemWidth, _itemHeight, Pattern);
_animDelay = (10 - MaxRows) * 3.01 + 4;
var tiles = isPractice ? await _gamePanelHelper.CreatePracticeGameTiles() :
await _gamePanelHelper.CreateGameTiles(imageFile);
GameTile[] gameTiles = new GameTile[MaxRows * MaxCols];
for (int i = 0; i < (MaxRows * MaxCols); i++)
{
int index = _gamePanelHelper.GetIndexFromCellIndex(i);
GameTile child = tiles.ElementAt(index);
if (child != null)
{
Children.Add(child);
gameTiles[i] = child;
child.Initialize(i, this);
// Initialize its RenderTransform
child.RenderTransform = _gamePanelHelper.CreateTransform(-_itemWidth, -_itemHeight, NORMAL_SCALE, NORMAL_SCALE);
}
}
if (isRestoring)
{
RestoreSuspendedState(serData);
_boardTiles.AddRange(Children.OfType<GameTile>().OrderBy(x => x.CurrentIndex));
}
else
{
// Shuffle the tiles
_boardTiles.AddRange(_gamePanelHelper.ShuffleGameTiles(gameTiles.AsEnumerable()));
}
InvalidateMeasure();
IsComposing = true;
// Raise the InitializationCompleted event if it is not restoring
if (!isRestoring)
{
var eventHandler = this.InitializationCompleted;
if (eventHandler != null)
eventHandler(this, null);
}
});
}
internal void SolveGame()
{
IsComposing = false;
_intermediateTiles.Clear();
_intermediateTiles.AddRange(_boardTiles);
_boardTiles.Clear();
int count = 0;
foreach (UIElement child in Children)
{
((GameTile)child).CurrentIndex = count++;
_boardTiles.Add(child);
}
InvalidateArrange();
}
internal void ResumeGame()
{
_boardTiles.Clear();
int count = 0;
foreach (UIElement child in _intermediateTiles)
{
((GameTile)child).CurrentIndex = count++;
_boardTiles.Add(child);
}
InvalidateArrange();
IsComposing = true;
}
internal void ResetGame()
{
foreach (GameTile tile in _boardTiles)
{
tile.CurrentIndex = tile.InitialIndex;
}
_boardTiles = _boardTiles.OfType<GameTile>().OrderBy(x => x.InitialIndex).ToList<UIElement>();
InvalidateArrange();
IsComposing = true;
}
internal SerializedGameTile[] GetSuspendedState()
{
List<SerializedGameTile> result = new List<SerializedGameTile>();
foreach (GameTile tile in Children)
{
result.Add(tile.Serialize());
}
return result.ToArray();
}
internal void RestoreSuspendedState(SerializedGameTile[] serData)
{
var sortedData = serData.OrderBy(x => x.HomeIndex);
int count = 0;
foreach (GameTile tile in Children)
{
tile.Deserialize(sortedData.ElementAt(count++));
}
}
#endregion
#region Helpers
/// <summary>
/// Iterates through all the fluid elements and animate their
/// movement to their new location.
/// </summary>
private void UpdateFluidLayout(bool showEasing = true)
{
if (_boardTiles == null)
return;
int dragTileIndex = -1;
if (_dragTile != null)
dragTileIndex = _boardTiles.IndexOf(_dragTile);
// Iterate through all the fluid elements and animate their
// movement to their new location.
for (int index = 0; index < _boardTiles.Count; index++)
{
UIElement element = _boardTiles[index];
if (element == null)
continue;
// If an child is currently being dragged, then no need to animate it
if (index == dragTileIndex)
continue;
element.Arrange(new Rect(0, 0, element.DesiredSize.Width,
element.DesiredSize.Height));
// Get the cell position of the current index
Point pos = _gamePanelHelper.GetPointFromIndex(index);
Storyboard transition;
// Is the child being animated the same as the child which was last dragged?
if (element == _lastDragElement)
{
if (!showEasing)
{
// Create the Storyboard for the transition
transition = _gamePanelHelper.CreateTransition(element, pos, FIRST_TIME_ANIMATION_DURATION, null);
}
else
{
// Is easing function specified for the animation?
TimeSpan duration = (DragEasing != null) ? DEFAULT_ANIMATION_TIME_WITH_EASING : DEFAULT_ANIMATION_TIME_WITHOUT_EASING;
// Create the Storyboard for the transition
transition = _gamePanelHelper.CreateTransition(element, pos, duration, DragEasing);
}
// When the user releases the drag child, it's Z-Index is set to 1 so that
// during the animation it does not go below other elements.
// After the animation has completed set its Z-Index to 0
transition.Completed += (s, e) =>
{
if (_lastDragElement != null)
{
_lastDragElement.SetValue(Canvas.ZIndexProperty, 0);
_lastDragElement = null;
}
};
}
else // It is a non-dragElement
{
if (!showEasing)
{
// Create the Storyboard for the transition
transition = _gamePanelHelper.CreateTransition(element, pos, TimeSpan.FromMilliseconds(FIRST_TIME_ANIMATION_DURATION.Milliseconds + (index * _animDelay)), null);
}
else
{
// Is easing function specified for the animation?
TimeSpan duration = (ElementEasing != null) ? TimeSpan.FromMilliseconds(DEFAULT_ANIMATION_TIME_WITH_EASING.Milliseconds + (index * _animDelay)) :
TimeSpan.FromMilliseconds(DEFAULT_ANIMATION_TIME_WITHOUT_EASING.Milliseconds + (index * _animDelay));
// Create the Storyboard for the transition
transition = _gamePanelHelper.CreateTransition(element, pos, duration, ElementEasing);
}
}
// Start the animation
transition.Begin();
}
}
/// <summary>
/// Moves the dragElement to the new Index
/// </summary>
/// <param name="newIndex">Index of the new location</param>
/// <returns>True-if dragElement was moved otherwise False</returns>
private bool UpdateDragElementIndex(int newIndex)
{
// Check if the dragElement is being moved to its current place
// If yes, then no need to proceed further. (Improves efficiency!)
int dragCellIndex = _boardTiles.IndexOf(_dragTile);
if (dragCellIndex == newIndex)
return false;
_boardTiles.RemoveAt(dragCellIndex);
_boardTiles.Insert(newIndex, _dragTile);
for (int i = 0; i < _boardTiles.Count; i++)
{
GameTile bTile = _boardTiles[i] as GameTile;
if (bTile != null)
{
bTile.CurrentIndex = i;
}
}
if (_dragTile is GameTile)
{
((GameTile)_dragTile).CurrentIndex = newIndex;
}
return true;
}
/// <summary>
/// Removes all the children from the FluidGameBoard
/// </summary>
private void CleanUp()
{
foreach (GameTile gTile in Children)
{
gTile.CleanUp();
}
_boardTiles.Clear();
Children.Clear();
}
async private Task CheckIfGameOverAsync()
{
bool gameOver = true;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
foreach (GameTile tile in _boardTiles)
{
if ((tile != null) && (!tile.IsHome))
{
gameOver = false;
break;
}
}
if (gameOver)
{
var eventHandler = this.GameOver;
if (eventHandler != null)
eventHandler(this, null);
}
});
}
#endregion
#region FluidDrag Event Handlers
/// <summary>
/// Handler for the event when the user starts dragging the dragElement.
/// </summary>
/// <param name="child">UIElement being dragged</param>
/// <param name="position">Position in the child where the user clicked</param>
async internal void BeginFluidDragAsync(UIElement child, Point position, Pointer pointer)
{
if ((child == null) || (!IsComposing))
return;
// Call the event handler core on the Dispatcher. (Improves efficiency!)
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
child.Opacity = DragOpacity;
child.SetValue(Canvas.ZIndexProperty, Z_INDEX_DRAG);
// Capture further mouse events
child.CapturePointer(pointer);
_dragTile = child;
_lastDragElement = null;
_dragStartIndex = _boardTiles.IndexOf(child);
_dragEndIndex = -1;
// Since we are scaling the dragElement by DragScale, the clickPoint also shifts
_dragStartPoint = new Point(position.X * DragScale, position.Y * DragScale);
});
}
/// <summary>
/// Handler for the event when the user drags the dragElement.
/// </summary>
/// <param name="child">UIElement being dragged</param>
/// <param name="position">Position where the user clicked w.r.t. the UIElement being dragged</param>
/// <param name="positionInParent">Position where the user clicked w.r.t. the FluidGameBoard (the parentFWPanel of the UIElement being dragged</param>
async internal void FluidDragAsync(UIElement child, Point position, Point positionInParent)
{
if ((child == null) || (!IsComposing))
return;
// Call the event handler core on the Dispatcher. (Improves efficiency!)
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if ((_dragTile != null) && (_gamePanelHelper != null))
{
// Create the new transform of the tile being dragged
_dragTile.RenderTransform = _gamePanelHelper.CreateTransform(positionInParent.X - _dragStartPoint.X,
positionInParent.Y - _dragStartPoint.Y,
DragScale,
DragScale);
// Get the index in the fluidElements list corresponding to the current mouse location
Point currentPt = positionInParent;
int index = _gamePanelHelper.GetIndexFromPoint(currentPt);
// If no valid cell index is obtained, add the child to the end of the
// fluidElements list.
if ((index == -1) || (index >= _boardTiles.Count))
{
index = _boardTiles.Count - 1;
}
// If the dragElement is moved to a new location, then only
// call the updation of the layout.
if (UpdateDragElementIndex(index))
{
InvalidateArrange();
}
}
});
}
/// <summary>
/// Handler for the event when the user stops dragging the dragElement and releases it.
/// </summary>
/// <param name="child">UIElement being dragged</param>
/// <param name="position">Position where the user clicked w.r.t. the UIElement being dragged</param>
/// <param name="positionInParent">Position where the user clicked w.r.t. the FluidGameBoard (the parentFWPanel of the UIElement being dragged</param>
async internal void EndFluidDragAsync(UIElement child, Point position, Point positionInParent, Pointer pointer)
{
if ((child == null) || (!IsComposing))
return;
// Call the event handler core on the Dispatcher. (Improves efficiency!)
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
if ((_dragTile != null) && (_gamePanelHelper != null))
{
// Create the final transform for the tile being dragged
_dragTile.RenderTransform = _gamePanelHelper.CreateTransform(positionInParent.X - _dragStartPoint.X,
positionInParent.Y - _dragStartPoint.Y,
DragScale,
DragScale);
// Remove the transparency
child.Opacity = NORMAL_OPACITY;
// Z-Index is set to 1 so that during the animation it does not go below other elements.
child.SetValue(Canvas.ZIndexProperty, Z_INDEX_INTERMEDIATE);
// Release the mouse capture
child.ReleasePointerCapture(pointer);
// Reference used to set the Z-Index to 0 during the UpdateFluidLayout
_lastDragElement = _dragTile;
_dragEndIndex = _boardTiles.IndexOf(_dragTile);
_dragTile = null;
// Raise the TileMoved event if the move is valid
if (_dragStartIndex != _dragEndIndex)
{
var eventHandler = this.TileMoved;
if (eventHandler != null)
eventHandler(this, null);
}
await CheckIfGameOverAsync();
}
InvalidateArrange();
});
}
#endregion
}
GamePanelHelper
此类是GamePanel类的辅助类,负责GamePanel运行所需的大部分数学计算,例如计算拼图块的位置、将图像分割成拼图块、创建拼图块流畅移动的动画等。
internal sealed class GamePanelHelper
{
#region Fields
private Size _panelSize;
private Size _cellSize;
private int _maxRows;
private int _maxCols;
private PatternType _arrangeStyle;
Dictionary<int, Cell> _cellIndex;
CoreDispatcher _dispatcher = null;
int _imgWidth = 0;
int _imgHeight = 0;
byte[] _imgBuffer = null;
#endregion
#region APIs
/// <summary>
/// Calculates the initial location of the child in the FluidWrapPanel
/// when the child is added.
/// </summary>
/// <param name="index">Index of the child in the FluidWrapPanel</param>
/// <returns></returns>
internal Point GetInitialLocationOfChild(int index)
{
Point result = new Point();
int row, column;
GetCellFromIndex(index, out row, out column);
int maxRows = (Int32)Math.Floor(_panelSize.Height / _cellSize.Height);
int maxCols = (Int32)Math.Floor(_panelSize.Width / _cellSize.Width);
bool isLeft = true;
bool isTop = true;
bool isCenterHeight = false;
bool isCenterWidth = false;
int halfRows = 0;
int halfCols = 0;
halfRows = (int)((double)maxRows / (double)2);
// Even number of rows
if ((maxRows % 2) == 0)
{
isTop = row < halfRows;
}
// Odd number of rows
else
{
if (row == halfRows)
{
isCenterHeight = true;
isTop = false;
}
else
{
isTop = row < halfRows;
}
}
halfCols = (int)((double)maxCols / (double)2);
// Even number of columns
if ((maxCols % 2) == 0)
{
isLeft = column < halfCols;
}
// Odd number of columns
else
{
if (column == halfCols)
{
isCenterWidth = true;
isLeft = false;
}
else
{
isLeft = column < halfCols;
}
}
if (isCenterHeight && isCenterWidth)
{
double posX = (halfCols) * _cellSize.Width;
double posY = (halfRows + 2) * _cellSize.Height;
return new Point(posX, posY);
}
if (isCenterHeight)
{
if (isLeft)
{
double posX = ((halfCols - column) + 1) * _cellSize.Width;
double posY = (halfRows) * _cellSize.Height;
result = new Point(-posX, posY);
}
else
{
double posX = ((column - halfCols) + 1) * _cellSize.Width;
double posY = (halfRows) * _cellSize.Height;
result = new Point(_panelSize.Width + posX, posY);
}
return result;
}
if (isCenterWidth)
{
if (isTop)
{
double posX = (halfCols) * _cellSize.Width;
double posY = ((halfRows - row) + 1) * _cellSize.Height;
result = new Point(posX, -posY);
}
else
{
double posX = (halfCols) * _cellSize.Width;
double posY = ((row - halfRows) + 1) * _cellSize.Height;
result = new Point(posX, _panelSize.Height + posY);
}
return result;
}
if (isTop)
{
if (isLeft)
{
double posX = ((halfCols - column) + 1) * _cellSize.Width;
double posY = ((halfRows - row) + 1) * _cellSize.Height;
result = new Point(-posX, -posY);
}
else
{
double posX = ((column - halfCols) + 1) * _cellSize.Width;
double posY = ((halfRows - row) + 1) * _cellSize.Height;
result = new Point(posX + _panelSize.Width, -posY);
}
}
else
{
if (isLeft)
{
double posX = ((halfCols - column) + 1) * _cellSize.Width;
double posY = ((row - halfRows) + 1) * _cellSize.Height;
result = new Point(-posX, _panelSize.Height + posY);
}
else
{
double posX = ((column - halfCols) + 1) * _cellSize.Width;
double posY = ((row - halfRows) + 1) * _cellSize.Height;
result = new Point(posX + _panelSize.Width, _panelSize.Height + posY);
}
}
return result;
}
/// <summary>
/// Initializes the FluidLayoutManager
/// </summary>
/// <param name="panelWidth">Width of the FluidWrapPanel</param>
/// <param name="panelHeight">Height of the FluidWrapPanel</param>
/// <param name="cellWidth">Width of each child in the FluidWrapPanel</param>
/// <param name="cellHeight">Height of each child in the FluidWrapPanel</param>
/// <param name="orientation">Orientation of the panel - Horizontal or Vertical</param>
internal void Initialize(CoreDispatcher dispatcher, double panelWidth, double panelHeight, int maxRows, int maxCols, double cellWidth, double cellHeight, PatternType style)
{
_dispatcher = dispatcher;
this._maxRows = maxRows;
this._maxCols = maxCols;
_arrangeStyle = style;
_cellIndex = IndexGenerator.GenerateIndex(maxRows, maxCols, style);
if (panelWidth <= 0.0d)
panelWidth = cellWidth;
if (panelHeight <= 0.0d)
panelHeight = cellHeight;
if ((cellWidth <= 0.0d) || (cellHeight <= 0.0d))
{
return;
}
if ((_panelSize.Width != panelWidth) ||
(_panelSize.Height != panelHeight) ||
(_cellSize.Width != cellWidth) ||
(_cellSize.Height != cellHeight))
{
_panelSize = new Size(panelWidth, panelHeight);
_cellSize = new Size(cellWidth, cellHeight);
}
}
/// <summary>
/// Gets the index of the child from the given cell index
/// </summary>
/// <param name="index">Cell Index</param>
/// <returns>True Index</returns>
internal int GetIndexFromCellIndex(int index)
{
int result = -1;
if (_cellIndex.ContainsKey(index))
{
Cell cell = _cellIndex[index];
result = ((cell.Row * _maxCols) + cell.Column);
}
return result;
}
/// <summary>
/// Provides the index of the child (in the FluidWrapPanel's children) from the given row and column
/// </summary>
/// <param name="row">Row</param>
/// <param name="column">Column</param>
/// <returns>Index</returns>
internal int GetIndexFromCell(int row, int column)
{
int result = -1;
if ((row >= 0) && (column >= 0))
{
Cell cell = new Cell { Row = row, Column = column };
result = _cellIndex.Where(x => x.Value == cell).Select(x => x.Key).FirstOrDefault();
}
return result;
}
/// <summary>
/// Provides the index of the child (in the FluidWrapPanel's children) from the given point
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
internal int GetIndexFromPoint(Point p)
{
int result = -1;
if ((p.X > 0.00D) &&
(p.X < _panelSize.Width) &&
(p.Y > 0.00D) &&
(p.Y < _panelSize.Height))
{
int row;
int column;
GetCellFromPoint(p, out row, out column);
result = GetIndexFromCell(row, column);
}
return result;
}
/// <summary>
/// Provides the row and column of the child based on its index in the FluidWrapPanel.Children
/// </summary>
/// <param name="index">Index</param>
/// <param name="row">Row</param>
/// <param name="column">Column</param>
internal void GetCellFromIndex(int index, out int row, out int column)
{
row = column = -1;
if (_cellIndex.ContainsKey(index))
{
Cell cell = _cellIndex[index];
row = cell.Row;
column = cell.Column;
}
}
/// <summary>
/// Provides the row and column of the child based on its location in the FluidWrapPanel
/// </summary>
/// <param name="p">Location of the child in the parent</param>
/// <param name="row">Row</param>
/// <param name="column">Column</param>
internal void GetCellFromPoint(Point p, out int row, out int column)
{
row = column = -1;
if ((p.X < 0.00D) ||
(p.X > _panelSize.Width) ||
(p.Y < 0.00D) ||
(p.Y > _panelSize.Height))
{
return;
}
row = (int)(p.Y / _cellSize.Height);
column = (int)(p.X / _cellSize.Width);
}
/// <summary>
/// Provides the location of the child in the FluidWrapPanel based on the given row and column
/// </summary>
/// <param name="row">Row</param>
/// <param name="column">Column</param>
/// <returns>Location of the child in the panel</returns>
internal Point GetPointFromCell(int row, int column)
{
Point result = new Point();
if ((row >= 0) && (column >= 0))
{
result = new Point(_cellSize.Width * column, _cellSize.Height * row);
}
return result;
}
/// <summary>
/// Provides the location of the child in the FluidWrapPanel based on the given row and column
/// </summary>
/// <param name="index">Index</param>
/// <returns>Location of the child in the panel</returns>
internal Point GetPointFromIndex(int index)
{
Point result = new Point();
if (index >= 0)
{
int row;
int column;
GetCellFromIndex(index, out row, out column);
result = GetPointFromCell(row, column);
}
return result;
}
/// <summary>
/// Creates a TransformGroup based on the given Translation, Scale and Rotation
/// </summary>
/// <param name="transX">Translation in the X-axis</param>
/// <param name="transY">Translation in the Y-axis</param>
/// <param name="scaleX">Scale factor in the X-axis</param>
/// <param name="scaleY">Scale factor in the Y-axis</param>
/// <param name="rotAngle">Rotation</param>
/// <returns>TransformGroup</returns>
internal TransformGroup CreateTransform(double transX, double transY, double scaleX, double scaleY, double rotAngle = 0.0D)
{
TranslateTransform translation = new TranslateTransform();
translation.X = transX;
translation.Y = transY;
ScaleTransform scale = new ScaleTransform();
scale.ScaleX = scaleX;
scale.ScaleY = scaleY;
//RotateTransform rotation = new RotateTransform();
//rotation.Angle = rotAngle;
TransformGroup transform = new TransformGroup();
// THE ORDER OF TRANSFORM IS IMPORTANT
// First, scale, then rotate and finally translate
transform.Children.Add(scale);
//transform.Children.Add(rotation);
transform.Children.Add(translation);
return transform;
}
/// <summary>
/// Creates the storyboard for animating a child from its old location to the new location.
/// The Translation and Scale properties are animated.
/// </summary>
/// <param name="element">UIElement for which the storyboard has to be created</param>
/// <param name="newLocation">New location of the UIElement</param>
/// <param name="period">Duration of animation</param>
/// <param name="easing">Easing function</param>
/// <returns>Storyboard</returns>
internal Storyboard CreateTransition(UIElement element, Point newLocation, TimeSpan period, EasingFunctionBase easing)
{
Duration duration = new Duration(period);
// Animate X
DoubleAnimation translateAnimationX = new DoubleAnimation();
translateAnimationX.To = newLocation.X;
translateAnimationX.Duration = duration;
if (easing != null)
translateAnimationX.EasingFunction = easing;
Storyboard.SetTarget(translateAnimationX, element);
Storyboard.SetTargetProperty(translateAnimationX,
"(UIElement.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.X)");
// Animate Y
DoubleAnimation translateAnimationY = new DoubleAnimation();
translateAnimationY.To = newLocation.Y;
translateAnimationY.Duration = duration;
if (easing != null)
translateAnimationY.EasingFunction = easing;
Storyboard.SetTarget(translateAnimationY, element);
Storyboard.SetTargetProperty(translateAnimationY,
"(UIElement.RenderTransform).(TransformGroup.Children)[1].(TranslateTransform.Y)");
// Animate ScaleX
DoubleAnimation scaleAnimationX = new DoubleAnimation();
scaleAnimationX.To = 1.0D;
scaleAnimationX.Duration = duration;
if (easing != null)
scaleAnimationX.EasingFunction = easing;
Storyboard.SetTarget(scaleAnimationX, element);
Storyboard.SetTargetProperty(scaleAnimationX,
"(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)");
// Animate ScaleY
DoubleAnimation scaleAnimationY = new DoubleAnimation();
scaleAnimationY.To = 1.0D;
scaleAnimationY.Duration = duration;
if (easing != null)
scaleAnimationY.EasingFunction = easing;
Storyboard.SetTarget(scaleAnimationY, element);
Storyboard.SetTargetProperty(scaleAnimationY,
"(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)");
Storyboard sb = new Storyboard();
sb.Duration = duration;
sb.Children.Add(translateAnimationX);
sb.Children.Add(translateAnimationY);
sb.Children.Add(scaleAnimationX);
sb.Children.Add(scaleAnimationY);
return sb;
}
async internal Task<IEnumerable<GameTile>> CreateGameTiles(StorageFile imageFile)
{
IEnumerable<GameTile> result = null;
if (imageFile != null)
{
await ObtainImageDetailsAsync(imageFile);
result = await GenerateGameTilesAsync(imageFile);
}
return result;
}
async internal Task<IEnumerable<GameTile>> CreatePracticeGameTiles()
{
GameTile[] result = null;
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
int totalNum = _maxRows * _maxCols;
result = new GameTile[totalNum];
double tileWidth = _panelSize.Width / _maxCols;
double tileHeight = _panelSize.Height / _maxRows;
for (int i = 0; i < _maxRows; i++)
{
for (int j = 0; j < _maxCols; j++)
{
int index = i * _maxCols + j;
GameTile gTile = new GameTile
{
Width = tileWidth,
Height = tileHeight,
DisplayImage = null,
IsContentImage = false,
DisplayValue = (index + 1).ToString(),
HomeIndex = index,
InitialIndex = index,
CurrentIndex = index
};
result[index] = gTile;
}
}
});
return result;
}
/// <summary>
/// Shuffles the Game Tiles
/// </summary>
/// <param name="tiles">GameTiles array</param>
/// <returns>Shuffled GameTiles array</returns>
internal IEnumerable<GameTile> ShuffleGameTiles(IEnumerable<GameTile> tiles)
{
int totalNum = _maxRows * _maxCols;
GameTile[] result = new GameTile[totalNum];
//// TODO: REMOVE
//result[0] = tiles.ElementAt(1);
//result[1] = tiles.ElementAt(0);
//for (int i = 2; i < tiles.Count(); i++)
//{
// result[i] = tiles.ElementAt(i);
//}
Random rnd = new Random();
List<int> indices = new List<int>();
for (int i = 0; i < totalNum; i++)
{
indices.Add(i);
}
int currCount = 0;
while (indices.Count > 1)
{
int nextRnd = rnd.Next(indices.Count);
if (indices[nextRnd] != currCount)
{
tiles.ElementAt(currCount).InitialIndex = indices[nextRnd];
tiles.ElementAt(currCount).CurrentIndex = indices[nextRnd];
result[indices[nextRnd]] = tiles.ElementAt(currCount);
indices.RemoveAt(nextRnd);
currCount++;
}
}
tiles.ElementAt(currCount).CurrentIndex = indices[0];
result[indices[0]] = tiles.ElementAt(currCount);
return result;
}
#endregion
#region Helpers
async Task ObtainImageDetailsAsync(StorageFile storageFile)
{
using (IRandomAccessStream fileStream = await storageFile.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
BitmapFrame frame = await decoder.GetFrameAsync(0);
PixelDataProvider framePixelProvider = await frame.GetPixelDataAsync();
try
{
_imgWidth = (int)decoder.PixelWidth;
_imgHeight = (int)decoder.PixelHeight;
_imgBuffer = framePixelProvider.DetachPixelData();
}
catch (Exception e)
{
string msg = e.Message;
}
}
}
async Task<IEnumerable<GameTile>> GenerateGameTilesAsync(StorageFile imageFile)
{
List<GameTile> result = new List<GameTile>();
double tileWidth = _imgWidth / (double)_maxCols;
double tileHeight = _imgHeight / (double)_maxRows;
for (int i = 0; i < _maxRows; i++)
{
for (int j = 0; j < _maxCols; j++)
{
int stride = _imgWidth * 4;
List<byte> frameBytes = new List<byte>();
int startRow = (int)(i * tileHeight);
int endRow = (int)(startRow + tileHeight);
int startCol = (int)(j * tileWidth);
int endCol = (int)(startCol + tileWidth);
try
{
for (int r = startRow; r < endRow; r++)
{
for (int c = startCol; c < endCol; c++)
{
frameBytes.Add(_imgBuffer[r * stride + c * 4 + 0]);
frameBytes.Add(_imgBuffer[r * stride + c * 4 + 1]);
frameBytes.Add(_imgBuffer[r * stride + c * 4 + 2]);
frameBytes.Add(_imgBuffer[r * stride + c * 4 + 3]);
}
}
}
catch (IndexOutOfRangeException)
{
}
try
{
using (InMemoryRandomAccessStream ras = new InMemoryRandomAccessStream())
{
BitmapEncoder enc = null;
if (imageFile.FileType == ".png")
enc = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, ras);
else
enc = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, ras);
// Write pixel data
enc.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, (uint)tileWidth, (uint)tileHeight, 96, 96, frameBytes.ToArray());
// Flush (all data is in "ras")
await enc.FlushAsync();
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
BitmapImage bImg = new BitmapImage();
ras.Seek(0);
bImg.SetSource(ras);
ImageBrush imgBrush = new ImageBrush() { Stretch = Stretch.Fill };
imgBrush.ImageSource = bImg;
int index = i * _maxCols + j;
GameTile gTile = new GameTile
{
Width = _cellSize.Width,
Height = _cellSize.Height,
DisplayImage = imgBrush,
IsContentImage = true,
HomeIndex = index,
InitialIndex = index,
CurrentIndex = index
};
result.Add(gTile);
});
}
}
catch (Exception e)
{
string msg = e.Message;
}
}
}
return result;
}
#endregion
}
GameTile
GameTile是负责显示每个拼图块内容的控件。当GameTile不在正确位置时,它会用粗黑边框包围。一旦拼图块被放置在正确的位置,粗边框将被移除。
<UserControl x:Class="FloTiles.Cortex.GameTile"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FloTiles.Cortex"
xmlns:converters="using:FloTiles.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityReverseConverter"
IsReverse="True"></converters:BooleanToVisibilityConverter>
<SolidColorBrush x:Key="SolidBrush"
Color="#0276FD"></SolidColorBrush>
<LinearGradientBrush x:Key="GradientBrush"
EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#FF0276FD"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#FF0276FD"
Offset="0.011" />
</LinearGradientBrush>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Border Name="BG"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="Transparent"
BorderThickness="0">
</Border>
<Border Name="TileBG"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{Binding DisplayImage}"
BorderBrush="Transparent"
BorderThickness="0" />
<Viewbox Margin="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="Segoe WP"
FontSize="58"
FontWeight="Light"
Foreground="White"
Text="{Binding Path=DisplayValue}" />
</Viewbox>
<Border Name="TileBorder"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent"
BorderBrush="Black"
BorderThickness="4"
Visibility="{Binding Path=IsHome,
Converter={StaticResource BoolToVisibilityReverseConverter}}" />
</Grid>
</UserControl>
public sealed partial class GameTile : UserControl
{
#region Fields
GamePanel _parent = null;
Brush _solidBrush = null;
Brush _gradientBrush = null;
#endregion
#region Dependency Properties
...
#endregion
#endregion
#region Construction / Initialization
public GameTile()
{
InitializeComponent();
_solidBrush = this.Resources["SolidBrush"] as SolidColorBrush;
_gradientBrush = this.Resources["GradientBrush"] as LinearGradientBrush;
BG.Background = IsHome ? _solidBrush : _gradientBrush;
this.DataContext = this;
}
#endregion
#region APIs
public void Initialize(Int32 index, GamePanel parentPanel)
{
HomeIndex = index;
InitialIndex = index;
CurrentIndex = index;
_parent = parentPanel;
this.PointerPressed += OnPointerPressed;
this.PointerMoved += OnPointerMoved;
this.PointerReleased += OnPointerReleased;
}
public void CleanUp()
{
_parent = null;
this.PointerPressed -= OnPointerPressed;
this.PointerMoved -= OnPointerMoved;
this.PointerReleased -= OnPointerReleased;
}
public SerializedGameTile Serialize()
{
return new SerializedGameTile
{
HomeIndex = this.HomeIndex,
InitialIndex = this.InitialIndex,
CurrentIndex = this.CurrentIndex
};
}
public void Deserialize(SerializedGameTile serTile)
{
if (serTile != null)
{
this.HomeIndex = serTile.HomeIndex;
this.InitialIndex = serTile.InitialIndex;
this.CurrentIndex = serTile.CurrentIndex;
}
}
#endregion
#region Event Handlers
void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
if (_parent != null)
{
PointerPoint position = e.GetCurrentPoint(this);
_parent.BeginFluidDragAsync(this, position.Position, e.Pointer);
}
}
void OnPointerMoved(object sender, PointerRoutedEventArgs e)
{
if (_parent != null)
{
PointerPoint position = e.GetCurrentPoint(this);
PointerPoint positionInParent = e.GetCurrentPoint(_parent);
_parent.FluidDragAsync(this, position.Position, positionInParent.Position);
}
}
void OnPointerReleased(object sender, PointerRoutedEventArgs e)
{
if (_parent != null)
{
PointerPoint position = e.GetCurrentPoint(this);
PointerPoint positionInParent = e.GetCurrentPoint(_parent);
_parent.EndFluidDragAsync(this, position.Position, positionInParent.Position, e.Pointer);
}
}
#endregion
}
DataModel
此模块包含封装在各种模块之间流动的数据的类。以下是其中一些:
MenuItemBase
此类从BindableBase
派生,充当MenuItem
和SubMenuItem
类的基类,它们分别代表游戏中的Challenge
和Level。
[DataContract]
public abstract class MenuItemBase : BindableBase
{
private static Uri _baseUri = new Uri("ms-appx:///");
#region Properties
#region Status
private LevelStatusType _status = LevelStatusType.None;
/// <summary>
/// Gets or sets the Status property. This observable property
/// indicates status of the level.
/// </summary>
[DataMember]
public LevelStatusType Status
{
get { return _status; }
set { this.SetProperty(ref this._status, value); }
}
#endregion
#region Info
private GameInfo _info = null;
/// <summary>
/// Gets or sets the Info property. This observable property
/// indicates the information associated with this MenuItemBase.
/// </summary>
[DataMember]
public GameInfo Info
{
get { return _info; }
set { this.SetProperty(ref this._info, value); }
}
#endregion
#region Title
private string _title = string.Empty;
/// <summary>
/// Gets or sets the Title property. This observable property
/// indicates the title of the Group.
/// </summary>
[DataMember]
public string Title
{
get { return _title; }
set { this.SetProperty(ref this._title, value); }
}
#endregion
#region Description
private string _description = string.Empty;
/// <summary>
/// Gets or sets the Description property. This observable property
/// indicates the group description.
/// </summary>
[DataMember]
public string Description
{
get { return _description; }
set { this.SetProperty(ref this._description, value); }
}
#endregion
#region IconImagePath
private string _iconImagePath = string.Empty;
/// <summary>
/// Gets or sets the IconImagePath property. This observable property
/// indicates the image path.
/// </summary>
[DataMember]
public string IconImagePath
{
get { return _iconImagePath; }
set { this.SetProperty(ref this._iconImagePath, value); }
}
#endregion
#region IconImage
private ImageSource _iconImage = null;
public ImageSource IconImage
{
get
{
if ((this._iconImage == null) && (!String.IsNullOrWhiteSpace(this._iconImagePath)))
{
CreateIconImage();
}
return this._iconImage;
}
}
#endregion
#endregion
#region Helpers
private void CreateIconImage()
{
this._iconImage = new BitmapImage(new Uri(MenuItemBase._baseUri, this._iconImagePath));
}
#endregion
#region Construction / Initialization
public MenuItemBase(string uniqueId, PatternType pattern, GameLevelType level, string title, string description, string iconImagePath, LevelStatusType levelStatus = LevelStatusType.None)
{
_info = new GameInfo { UniqueId = uniqueId, Pattern = pattern, Level = level };
_status = levelStatus;
_title = title;
_description = description;
_iconImagePath = iconImagePath;
CreateIconImage();
}
#endregion
}
MenuItem
此类从MenuItemBase
派生,代表游戏中的Challenge。
[DataContract]
public class MenuItem : MenuItemBase
{
#region Properties
#region SubMenuItems
private ObservableCollection<SubMenuItem> _subMenuItems = new ObservableCollection<SubMenuItem>();
/// <summary>
/// Gets or sets the SubMenuItems property. This observable property
/// indicates the collection of submenu dataSource within a menu item.
/// </summary>
[DataMember]
public ObservableCollection<SubMenuItem> SubMenuItems
{
get { return _subMenuItems; }
set { this.SetProperty(ref this._subMenuItems, value); }
}
#endregion
#endregion
#region Construction / Initialization
public MenuItem(string uniqueId, PatternType pattern, GameLevelType level, string title, string description, string iconImagePath, LevelStatusType levelStatus = LevelStatusType.None) :
base(uniqueId, pattern, level, title, description, iconImagePath, levelStatus)
{
}
#endregion
#region APIs
public bool IsLocked
{
get
{
bool result = true;
if ((_subMenuItems != null) && (_subMenuItems.Count() > 0))
{
var unlockedItems = _subMenuItems.Where(s => (!s.Info.UniqueId.EndsWith("_L_0")) && (s.Status != LevelStatusType.None) && (s.Status != LevelStatusType.Locked));
if ((unlockedItems != null) && (unlockedItems.Count() > 0))
{
result = false;
}
}
return result;
}
}
#endregion
}
SubMenuItem
此类从MenuItemBase
派生,代表Challenge中的
Level。一个
MenuItems
包含多个SubMenuItems
的集合。
[DataContract]
public class SubMenuItem : MenuItemBase
{
#region Properties
#region Menu
#region ItemColor
private SolidColorBrush _itemColor = null;
/// <summary>
/// Gets or sets the ItemColor property. This observable property
/// indicates the color of the item.
/// </summary>
[DataMember]
public SolidColorBrush ItemColor
{
get { return _itemColor; }
set { this.SetProperty(ref this._itemColor, value); }
}
#endregion
#region ItemSpan
private int _itemSpan = 1;
/// <summary>
/// Gets or sets the ItemSpan property. This observable property
/// indicates the span of the item.
/// </summary>
[DataMember]
public int ItemSpan
{
get { return _itemSpan; }
set { this.SetProperty(ref this._itemSpan, value); }
}
#endregion
[DataMember]
private MenuItem _menu = null;
/// <summary>
/// Gets or sets the Menu property. This observable property
/// indicates the parent Menu Item.
/// </summary>
public MenuItem Menu
{
get { return _menu; }
set { this.SetProperty(ref this._menu, value); }
}
#endregion
#region TotalMoves
private string _totalMoves = Constants.NO_VALUE;
/// <summary>
/// Gets or sets the TotalMoves property. This observable property
/// indicates the total number of moves made to complete this level.
/// </summary>
public string TotalMoves
{
get { return _totalMoves; }
set { this.SetProperty(ref this._totalMoves, value); }
}
#endregion
#region TotalDuration
private string _totalDuration = Constants.NO_VALUE;
/// <summary>
/// Gets or sets the TotalDuration property. This observable property
/// indicates the total duration taken to complete this level.
/// </summary>
public string TotalDuration
{
get { return _totalDuration; }
set { this.SetProperty(ref this._totalDuration, value); }
}
#endregion
#region IsLevel
private bool _isLevel = true;
/// <summary>
/// Gets or sets the IsLevel property. This observable property
/// indicates whether this is a practices or a game level.
/// </summary>
public bool IsLevel
{
get { return _isLevel; }
set { this.SetProperty(ref this._isLevel, value); }
}
#endregion
#endregion
#region Construction / Initialization
public SubMenuItem(string uniqueId, PatternType pattern, GameLevelType level, string title, string description, string imagePath,
MenuItem parent, SolidColorBrush itemColor, int itemSpan = 1, LevelStatusType levelStatus = LevelStatusType.None, string moves = Constants.NO_VALUE, string duration = Constants.NO_VALUE, bool isLevel = true) :
base(uniqueId, pattern, level, title, description, imagePath, levelStatus)
{
_itemColor = itemColor;
_itemSpan = itemSpan;
_menu = parent;
_totalMoves = moves;
_totalDuration = duration;
_isLevel = isLevel;
}
#endregion
}
GroupData
此类代表MenuItem
及其SubMenuItems
集合的 agrupation。
public class GroupData : BindableBase
{
#region Menu
private MenuItem _menu = null;
/// <summary>
/// Gets or sets the Menu property. This observable property
/// indicates the Menu Item.
/// </summary>
public MenuItem Menu
{
get { return _menu; }
set { this.SetProperty(ref this._menu, value); }
}
#endregion
#region SubMenuItems
private ObservableCollection<SubMenuItem> _subMenuItems = null;
/// <summary>
/// Gets or sets the SubMenuItems property. This observable property
/// indicates the SubMenu Items.
/// </summary>
public ObservableCollection<SubMenuItem> SubMenuItems
{
get { return _subMenuItems; }
set { this.SetProperty(ref this._subMenuItems, value); }
}
#endregion
}
GameMenuSource
此类包含硬编码的MenuItems
和SubMenuItems
列表,分别代表游戏中的Challenges
及其相应的Levels。
public static class GameMenuSource
{
public static IEnumerable<MenuItem> GetMenuItems()
{
ObservableCollection<MenuItem> menuItems = new ObservableCollection<MenuItem>();
var menu1 = new MenuItem("PAT_1", PatternType.StraightTopLeft, GameLevelType.None, "Straight Shot Challenge - I", "The arrangement of tiles begins at the Top-Left end of the GameBoard and moves right towards the last column on the same row. Once the last column of the row is reached, it continues from the first column of the next row.", "Assets/Icons/Patterns/Pattern01.png");
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_0", PatternType.StraightTopLeft, GameLevelType.Level0, "Practice Level", "Practice your moves.", "Assets/Icons/Levels/Level00.png", menu1, new SolidColorBrush(Colors.Orange), 2, isLevel: false, levelStatus: LevelStatusType.Playing));
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_1", PatternType.StraightTopLeft, GameLevelType.Level1, "Level 1", "3 x 3 Puzzle.", "Assets/Icons/Levels/Level01.png", menu1, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 1, LevelStatusType.Playing));
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_2", PatternType.StraightTopLeft, GameLevelType.Level2, "Level 2", "4 x 4 Puzzle.", "Assets/Icons/Levels/Level02.png", menu1, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 1, LevelStatusType.Locked));
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_3", PatternType.StraightTopLeft, GameLevelType.Level3, "Level 3", "5 x 5 Puzzle.", "Assets/Icons/Levels/Level03.png", menu1, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 1, LevelStatusType.Locked));
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_4", PatternType.StraightTopLeft, GameLevelType.Level4, "Level 4", "6 x 6 Puzzle.", "Assets/Icons/Levels/Level04.png", menu1, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 1, LevelStatusType.Locked));
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_5", PatternType.StraightTopLeft, GameLevelType.Level5, "Level 5", "7 x 7 Puzzle.", "Assets/Icons/Levels/Level05.png", menu1, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 1, LevelStatusType.Locked));
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_6", PatternType.StraightTopLeft, GameLevelType.Level6, "Level 6", "8 x 8 Puzzle.", "Assets/Icons/Levels/Level06.png", menu1, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 1, LevelStatusType.Locked));
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_7", PatternType.StraightTopLeft, GameLevelType.Level7, "Level 7", "9 x 9 Puzzle.", "Assets/Icons/Levels/Level07.png", menu1, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 1, LevelStatusType.Locked));
menu1.SubMenuItems.Add(new SubMenuItem("CHL_1_L_8", PatternType.StraightTopLeft, GameLevelType.Level8, "Level 8", "10 x 10 Puzzle.", "Assets/Icons/Levels/Level08.png", menu1, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 1, LevelStatusType.Locked));
menuItems.Add(menu1);
...
var menu11 = new MenuItem("PAT_11", PatternType.None, GameLevelType.None, "My FloTiles", "Create your own FloTile.", "Assets/Icons/Patterns/MyFloTiles.png");
menu11.SubMenuItems.Add(new SubMenuItem("MFT_CAM", PatternType.None, GameLevelType.None, "FloTile from Camera.", "Create a FloTile from your camera.", "Assets/Icons/Levels/Camera.png", menu11, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 2, isLevel: false));
menu11.SubMenuItems.Add(new SubMenuItem("MFT_ALB", PatternType.None, GameLevelType.None, "FloTile from Photo Album.", "Create a FloTile from your Photo Album.", "Assets/Icons/Levels/MyPictures.png", menu11, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 2, isLevel: false));
menu11.SubMenuItems.Add(new SubMenuItem("SCO_BRD", PatternType.None, GameLevelType.None, "Scoreboard", "Get the statistics of the completed levels.", "Assets/Icons/Levels/Scorecard.png", menu11, new SolidColorBrush(Color.FromArgb(255, 2, 118, 253)), 2, isLevel: false));
menuItems.Add(menu11);
return menuItems;
}
}
SerializedMenuItem
此类表示MenuItem
的序列化状态。
[DataContract]
public class SerializedMenuItem
{
[DataMember]
public string UniqueId { get; set; }
[DataMember]
public PatternType Pattern { get; set; }
[DataMember]
public GameLevelType Level { get; set; }
[DataMember]
public string Title { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public string IconImagePath { get; set; }
[DataMember]
public SerializedSubMenuItem[] Items { get; set; }
}
SerializedSubMenuItem
此类表示SubMenuItem
的序列化状态。
[DataContract]
public class SerializedSubMenuItem
{
[DataMember]
public string UniqueId { get; set; }
[DataMember]
public PatternType Pattern { get; set; }
[DataMember]
public GameLevelType Level { get; set; }
[DataMember]
public string Title { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public string IconImagePath { get; set; }
[DataMember]
public byte A { get; set; }
[DataMember]
public byte R { get; set; }
[DataMember]
public byte G { get; set; }
[DataMember]
public byte B { get; set; }
[DataMember]
public int ItemSpan { get; set; }
[DataMember]
public LevelStatusType Status { get; set; }
[DataMember]
public string TotalMoves { get; set; }
[DataMember]
public string TotalDuration { get; set; }
[DataMember]
public bool IsLevel { get; set; }
}
SerializedGameTile
此类表示GameTile
的序列化状态。
[DataContract]
public class SerializedGameTile
{
[DataMember]
public int HomeIndex { get; set; }
[DataMember]
public int InitialIndex { get; set; }
[DataMember]
public int CurrentIndex { get; set; }
}
视图
视图定义了根据用户交互呈现给用户的各种屏幕。这些视图派生自LayoutAwarePage,后者随Visual Studio 2012中的Windows Store应用模板提供。FloTiles应用中定义了5个视图:
- MainMenuPage
- SubMenuPage
- GamePage
- ScoreboardPage
- MyFloTilesPage
MainMenuPage
此页面以GridView格式显示Challenges
及其Levels。此页面还支持语义缩放。因此,它通过仅显示
Challenges
列表来响应捏合手势。它还支持纵向和贴靠状态。
代码
<common:LayoutAwarePage x:Name="pageRoot"
x:Class="FloTiles.Views.MainMenuPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FloTiles"
xmlns:common="using:FloTiles.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Foreground="Black"
mc:Ignorable="d"
Tag="3">
<Page.Resources>
<!--
Collection of grouped items displayed by this page, bound to a subset
of the complete item list because items in groups cannot be virtualized
-->
<CollectionViewSource x:Name="groupedItemsViewSource"
Source="{Binding MenuItems}"
IsSourceGrouped="true"
ItemsPath="SubMenuItems" />
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Background>
<ImageBrush ImageSource="../Assets/Images/Background/BG03.jpg"
Stretch="UniformToFill"
AlignmentX="Left"
AlignmentY="Top"></ImageBrush>
</Grid.Background>
<!-- Back button and Title Logo -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton"
Click="GoBack"
IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}"
Style="{StaticResource BackButtonStyle}" />
<Image x:Name="TitleLogo"
Grid.Column="1"
Source="../Assets/Icons/FloTilesHeader.png"
Stretch="Uniform"
Width="280"
Height="140"
Margin="-20,0,20,0"
IsHitTestVisible="False"
HorizontalAlignment="Left" />
</Grid>
<!-- Semantic Zoom -->
<SemanticZoom x:Name="SemanticZoomView"
Grid.Row="1">
<SemanticZoom.ZoomedInView>
<!-- Horizontal scrolling grid used in most view states -->
<GridView x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Padding="116,10,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource MainMenuTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="OnSubMenuItemSelected"
Tag="Horizontal">
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="4,10,0,10">
<Button AutomationProperties.Name="Group Title"
Style="{StaticResource TextPrimaryButtonStyle}"
Click="OnMenuItemSelected">
<StackPanel Orientation="Horizontal">
<Grid Width="40"
Height="40">
<Border Background="#0276FD"></Border>
<Border>
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
<Image Source="{Binding Menu.IconImage}"
Stretch="Uniform" />
</Border>
</Grid>
<TextBlock Text="{Binding Menu.Title}"
Margin="15,-4,10,10"
VerticalAlignment="Center"
Style="{StaticResource GroupHeaderTextStyle}" />
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Horizontal"
Margin="0,0,80,0"
MaximumRowsOrColumns="{Binding ElementName=pageRoot, Path=Tag}" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</SemanticZoom.ZoomedInView>
<SemanticZoom.ZoomedOutView>
<ListView x:Name="ZoomOut"
Width="1200"
Height="700"
Padding="10,50"
SelectionMode="None"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="200"
Height="200"
Background="#0276FD"
Margin="0,10">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
</Grid.RowDefinitions>
<Border>
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Border>
<Image Source="{Binding Group.Menu.IconImage}"
Stretch="Uniform"
Width="120"
Height="120"
Margin="-20,10,0,0"
VerticalAlignment="Top"
HorizontalAlignment="Center" />
</Border>
<Border Grid.RowSpan="2"
VerticalAlignment="Bottom"
Background="#323232"
Height="60">
<TextBlock Text="{Binding Group.Menu.Title}"
Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}"
FontFamily="Segoe UI Light"
FontWeight="Light"
FontSize="14"
Height="50"
Margin="5,0,0,0"
HorizontalAlignment="Left"
TextAlignment="Left"
VerticalAlignment="Center"
TextWrapping="Wrap" />
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid ItemWidth="200"
ItemHeight="200"
Orientation="Horizontal"
MaximumRowsOrColumns="10"
VerticalChildrenAlignment="Center" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
<!-- Vertical scrolling list only used when snapped -->
<ListView x:Name="SnappedListView"
AutomationProperties.AutomationId="SnappedListView"
AutomationProperties.Name="Grouped Items"
Grid.Row="1"
Visibility="Collapsed"
Margin="0,-10,0,0"
Padding="10,0,0,60"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard80ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="OnSubMenuItemSelected">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="7,10">
<Button AutomationProperties.Name="Group Title"
Style="{StaticResource TextPrimaryButtonStyle}"
Click="OnMenuItemSelected">
<StackPanel Orientation="Horizontal">
<Grid Width="40"
Height="40">
<Border Background="#0276FD"></Border>
<Border>
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
<Image Source="{Binding Menu.IconImage}"
Stretch="Uniform" />
</Border>
</Grid>
<TextBlock Text="{Binding Menu.Title}"
Margin="10,-4,10,10"
VerticalAlignment="Center"
Style="{StaticResource GroupHeaderTextStyleSnapped}"
TextWrapping="Wrap" />
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageRoot"
Storyboard.TargetProperty="Tag">
<DiscreteObjectKeyFrame KeyTime="0"
Value="3" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Filled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ZoomOut"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="900" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ZoomOut"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="700" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageRoot"
Storyboard.TargetProperty="Tag">
<DiscreteObjectKeyFrame KeyTime="0"
Value="3" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource PortraitBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ZoomOut"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="700" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ZoomOut"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="1200" />
</ObjectAnimationUsingKeyFrames>
<!-- CHECK -->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView"
Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0"
Value="96,55,10,56" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageRoot"
Storyboard.TargetProperty="Tag">
<DiscreteObjectKeyFrame KeyTime="0"
Value="2" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!--
The back button and title have different styles when snapped, and the list representation is substituted
for the grid displayed in all other view states
-->
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource SnappedBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="250" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="125" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="0,10,0,-10" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SnappedListView"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Visible" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SemanticZoomView"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Collapsed" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</common:LayoutAwarePage>
/// <summary>
/// A page that displays a grouped collection of dataSource.
/// </summary>
public sealed partial class MainMenuPage : FloTiles.Common.LayoutAwarePage
{
MessageDialog warningDialog = null;
public MainMenuPage()
{
this.InitializeComponent();
warningDialog = new MessageDialog("This level is locked. Please complete previous levels to unlock this level.", "FloTiles");
warningDialog.Commands.Add(new UICommand("Ok", null));
warningDialog.DefaultCommandIndex = 0;
warningDialog.CancelCommandIndex = 0;
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
/// <param name="gameState">Common Game State values</param>
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState, GameStateViewModel gameState)
{
IEnumerable<MenuItem> dataSource = (gameState != null) ? gameState.MenuItems : GameMenuSource.GetMenuItems();
var menuItems = from item in dataSource
group item by item into g
select new GroupData { Menu = g.Key, SubMenuItems = g.Key.SubMenuItems };
this.DefaultViewModel["MenuItems"] = menuItems.ToList();
ZoomOut.ItemsSource = groupedItemsViewSource.View.CollectionGroups;
}
async private void OnSubMenuItemSelected(object sender, ItemClickEventArgs e)
{
// Navigate to the appropriate destination page, configuring the new page
// by passing required information as a navigation parameter
var subMenuItem = e.ClickedItem as SubMenuItem;
if (subMenuItem != null)
{
if (subMenuItem.Status != Common.LevelStatusType.Locked)
{
string uniqueId = ((SubMenuItem)subMenuItem).Info.UniqueId;
Type pageType = LevelHelper.GetPageType(uniqueId);
this.Frame.Navigate(pageType, uniqueId);
}
else
await warningDialog.ShowAsync();
}
}
private void OnMenuItemSelected(object sender, RoutedEventArgs e)
{
// Determine what MenuItem the Button instance represents
var group = ((sender as FrameworkElement).DataContext) as GroupData;
if (group != null)
{
// Navigate to the appropriate destination page, configuring the new page
// by passing required information as a navigation parameter
string uniqueId = ((MenuItem)group.Menu).Info.UniqueId;
Type pageType = LevelHelper.GetPageType(uniqueId);
this.Frame.Navigate(pageType, uniqueId);
}
}
}
SubMenuPage
此页面以GridView格式显示用户选择的Challenge
及其Levels。此页面还支持纵向和贴靠状态。
代码
<common:LayoutAwarePage x:Name="pageRoot"
x:Class="FloTiles.Views.SubMenuPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FloTiles"
xmlns:common="using:FloTiles.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<!-- Collection of items displayed by this page -->
<CollectionViewSource x:Name="itemsViewSource"
Source="{Binding SubMenuItems}" />
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid DataContext="{Binding MenuItem}"
Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Background>
<ImageBrush ImageSource="../Assets/Images/Background/BG03.jpg"
Stretch="UniformToFill"
AlignmentX="Left"
AlignmentY="Top"></ImageBrush>
</Grid.Background>
<!-- Back button and Title Logo -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton"
Click="GoBack"
IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}"
Style="{StaticResource BackButtonStyle}" />
<Image x:Name="TitleLogo"
Grid.Column="1"
Source="../Assets/Icons/FloTilesHeader.png"
Stretch="Uniform"
Width="280"
Height="140"
IsHitTestVisible="False"
HorizontalAlignment="Left" />
</Grid>
<!-- Horizontal scrolling grid used in most view states -->
<GridView x:Name="LandscapeGridView"
AutomationProperties.AutomationId="LandscapeGridView"
AutomationProperties.Name="Items In Group"
TabIndex="1"
Grid.Row="1"
Padding="120,10,120,50"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
ItemTemplate="{StaticResource SubMenuItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="OnSubMenuItemSelected">
<GridView.Header>
<StackPanel Width="240"
Margin="0,4,14,0">
<TextBlock Text="{Binding Title}"
Margin="0,0,18,20"
Style="{StaticResource SubheaderTextStyle}"
MaxHeight="60" />
<Grid Height="240"
Margin="0"
Width="240"
AutomationProperties.Name="{Binding Title}">
<Border Background="#0276FD"></Border>
<Border>
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
<Image Source="{Binding IconImage}"
Stretch="Uniform" />
</Border>
</Grid>
<TextBlock Text="{Binding Description}"
Margin="0,0,10,0"
Style="{StaticResource BodyTextStyle}"
FontSize="16"
TextAlignment="Justify"/>
</StackPanel>
</GridView.Header>
<GridView.ItemContainerStyle>
<Style TargetType="FrameworkElement">
<Setter Property="Margin"
Value="52,0,0,10" />
</Style>
</GridView.ItemContainerStyle>
</GridView>
<!-- Vertical scrolling list only used when snapped -->
<ListView x:Name="SnappedListView"
AutomationProperties.AutomationId="ItemListView"
AutomationProperties.Name="Items In Group"
TabIndex="1"
Grid.Row="1"
Visibility="Collapsed"
Padding="10,0,0,60"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
ItemTemplate="{StaticResource Standard80ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="OnSubMenuItemSelected">
<ListView.Header>
<StackPanel Width="240"
Margin="4,-10,4,40">
<TextBlock Text="{Binding Title}"
Margin="0,0,18,20"
Style="{StaticResource SubheaderTextStyle}"
MaxHeight="60" />
<Grid Height="160"
Margin="0"
Width="160"
HorizontalAlignment="Left"
AutomationProperties.Name="{Binding Title}">
<Border Background="#0276FD"></Border>
<Border>
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
<Image Source="{Binding IconImage}"
Stretch="Uniform" />
</Border>
</Grid>
<TextBlock Text="{Binding Description}"
Margin="5,10,10,0"
FontSize="16"
Style="{StaticResource BodyTextStyle}"
TextAlignment="Justify"/>
</StackPanel>
</ListView.Header>
</ListView>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape" />
<VisualState x:Name="Filled" />
<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource PortraitBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<!-- CHECK -->
<!--<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView"
Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0"
Value="100,126,90,0" />
</ObjectAnimationUsingKeyFrames>-->
</Storyboard>
</VisualState>
<!--
The back button and title have different styles when snapped, and the list representation is substituted
for the grid displayed in all other view states
-->
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource SnappedBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="250" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="125" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="0,10,0,-10" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="LandscapeGridView"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Collapsed" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SnappedListView"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Visible" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</common:LayoutAwarePage>
/// <summary>
/// A page that displays an overview of a single group, including a preview of the dataSource
/// within the group.
/// </summary>
public sealed partial class SubMenuPage : FloTiles.Common.LayoutAwarePage
{
MessageDialog warningDialog = null;
public SubMenuPage()
{
this.InitializeComponent();
warningDialog = new MessageDialog("This level is locked. Please complete previous levels to unlock this level.", "FloTiles");
warningDialog.Commands.Add(new UICommand("Ok", null));
warningDialog.DefaultCommandIndex = 0;
warningDialog.CancelCommandIndex = 0;
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
/// <param name="gameState">Common Game State values</param>
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState, GameStateViewModel gameState)
{
// TODO: Assign a bindable group to this.DefaultViewModel["Group"]
// TODO: Assign a collection of bindable dataSource to this.DefaultViewModel["Items"]
var menuItem = gameState.GetMenuItem(navigationParameter as string);
this.DefaultViewModel["MenuItem"] = menuItem;
this.DefaultViewModel["SubMenuItems"] = menuItem.SubMenuItems;
}
async private void OnSubMenuItemSelected(object sender, ItemClickEventArgs e)
{
var subMenuItem = e.ClickedItem as SubMenuItem;
if (subMenuItem != null)
{
if (subMenuItem.Status != Common.LevelStatusType.Locked)
this.Frame.Navigate(typeof(GamePage), subMenuItem.Info.UniqueId);
else
await warningDialog.ShowAsync();
}
}
protected override void SaveState(Dictionary<string, object> pageState)
{
base.SaveState(pageState);
}
}
GamePage
此页面托管GamePanel
,这是实际发生操作的地方。在此页面的右上角,会显示已移动的次数和消耗的时间。此页面底部有应用栏,其中包含前面部分所述的4个选项。此页面还支持纵向和贴靠状态。
代码
<common:LayoutAwarePage x:Name="pageRoot"
x:Class="FloTiles.Views.GamePage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FloTiles"
xmlns:common="using:FloTiles.Common"
xmlns:cortex="using:FloTiles.Cortex"
xmlns:converters="using:FloTiles.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="White"
mc:Ignorable="d">
<Page.Resources>
<converters:ContentVisibilityConverter x:Key="ContentVisibilityHelper"></converters:ContentVisibilityConverter>
<converters:ContentEnablerConverter x:Key="ContentEnabler"></converters:ContentEnablerConverter>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHelper"></converters:BooleanToVisibilityConverter>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityReverseHelper"
IsReverse="True"></converters:BooleanToVisibilityConverter>
<converters:TimeConverter x:Key="TimeHelper"></converters:TimeConverter>
</Page.Resources>
<!-- Bottom AppBar -->
<Page.BottomAppBar>
<AppBar x:Name="GameBar"
Padding="10,0,10,0"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Playing|Paused|Solved}">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid x:Name="GameBarGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button x:Name="PauseGameBtn"
Grid.Column="0"
Click="OnPauseGame"
IsEnabled="{Binding GameStatus, Converter={StaticResource ContentEnabler}, ConverterParameter=Playing}"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Playing|Solved}"
Style="{StaticResource PauseGameAppBarButtonStyle}"></Button>
<Button x:Name="ResumeGameBtn"
Grid.Column="0"
Click="OnResumeGame"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Paused}"
Style="{StaticResource ResumeGameAppBarButtonStyle}"></Button>
<Button x:Name="ResetGameBtn"
Grid.Column="1"
Click="OnResetGame"
IsEnabled="{Binding GameStatus, Converter={StaticResource ContentEnabler}, ConverterParameter=Playing}"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Playing|Paused|Solved}"
Style="{StaticResource ResetGameAppBarButtonStyle}"></Button>
<Button x:Name="ShowPatternBtn"
Grid.Column="3"
Click="OnShowPattern"
IsEnabled="{Binding GameStatus, Converter={StaticResource ContentEnabler}, ConverterParameter=Playing|Paused}"
Visibility="{Binding IsPatternVisible, Converter={StaticResource BoolToVisibilityReverseHelper}}"
Style="{StaticResource ShowPatternAppBarButtonStyle}"></Button>
<Button x:Name="HidePatternBtn"
Grid.Column="3"
Click="OnHidePattern"
Visibility="{Binding IsPatternVisible, Converter={StaticResource BoolToVisibilityHelper}}"
Style="{StaticResource HidePatternAppBarButtonStyle}"></Button>
<Button x:Name="SolveBtn"
Grid.Column="4"
Click="OnSolve"
IsEnabled="{Binding GameStatus, Converter={StaticResource ContentEnabler}, ConverterParameter=Playing}"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Playing|Paused}"
Style="{StaticResource SolveAppBarButtonStyle}"></Button>
<Button x:Name="UnSolveBtn"
Grid.Column="4"
Click="OnContinueGaming"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Solved}"
Style="{StaticResource UnsolveAppBarButtonStyle}"></Button>
</Grid>
<Grid x:Name="SnappedGameBarGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="Collapsed">
</Grid>
</Grid>
</AppBar>
</Page.BottomAppBar>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Background>
<ImageBrush ImageSource="../Assets/Images/Background/Woven.png"
Stretch="None"
AlignmentX="Center"
AlignmentY="Center"></ImageBrush>
</Grid.Background>
<!-- Applause Sound -->
<MediaElement x:Name="applauseSound"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="10"
Height="10"
AutoPlay="False"
AudioCategory="GameMedia"
IsLooping="False"></MediaElement>
<!-- Back button and Title Logo -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton"
Click="GoBackInternal"
IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}"
Style="{StaticResource BackButtonStyle}" />
<Image x:Name="TitleLogo"
Grid.Column="1"
Source="../Assets/Icons/FloTilesHeader.png"
Stretch="Uniform"
Width="280"
Height="140"
IsHitTestVisible="False"
HorizontalAlignment="Left" />
</Grid>
<!-- Challenge Image-->
<Grid x:Name="ChallengeImage"
Width="80"
Height="80"
Margin="0,35,0,0"
VerticalAlignment="Top"
HorizontalAlignment="Center">
<Border Background="#0276FD"></Border>
<Border>
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
<Image Source="{Binding ChallengeImage}"
Stretch="Uniform" />
</Border>
</Grid>
<!-- KeepersGrid -->
<Grid x:Name="KeepersGrid"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Margin="0,20,20,0">
<Grid.RowDefinitions>
<RowDefinition Height="60"></RowDefinition>
<RowDefinition Height="60"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="TimeKeeper"
HorizontalAlignment="Right"
FontSize="42"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="Light"
Text="{Binding TotalDuration, Converter={StaticResource TimeHelper}}"></TextBlock>
<TextBlock x:Name="MoveKeeper"
HorizontalAlignment="Right"
Grid.Row="1"
FontSize="42"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="Light"
Text="{Binding TotalMoves}"></TextBlock>
</Grid>
<!-- Game Board -->
<Viewbox Margin="0"
Grid.Row="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Grid Margin="0">
<cortex:GamePanel x:Name="gameBoard"
Width="1280"
Height="853"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,20">
<cortex:GamePanel.ElementEasing>
<BackEase Amplitude="0.45"
EasingMode="EaseOut" />
</cortex:GamePanel.ElementEasing>
<cortex:GamePanel.DragEasing>
<BackEase Amplitude="0.65"
EasingMode="EaseOut" />
</cortex:GamePanel.DragEasing>
</cortex:GamePanel>
<!-- Loading Grid -->
<Grid x:Name="LoadingGrid"
Canvas.ZIndex="3"
Width="1280"
Height="853"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,20"
Background="#44111111"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Loading}">
<ProgressRing Foreground="LawnGreen"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="50"
Height="50"></ProgressRing>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="96"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="Light"
Margin="25,10"
Text="Loading..."></TextBlock>
</Grid>
<!-- Initializing Grid -->
<Grid x:Name="InitializingGrid"
Canvas.ZIndex="3"
Width="1280"
Height="853"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,20"
Background="#44111111"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Initializing}">
<ProgressRing Foreground="LawnGreen"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="50"
Height="50"></ProgressRing>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="96"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="ExtraLight"
Margin="25,10"
Text="Initializing..."></TextBlock>
</Grid>
<!-- Tap To Play Grid -->
<Grid x:Name="TapToPlayGrid"
Canvas.ZIndex="3"
Width="1280"
Height="853"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,20"
Background="#AA121212"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=TapToPlay}"
PointerPressed="OnTapToStart">
<TextBlock x:Name="StartGameMsg"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="96"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="ExtraLight"
Margin="25,10"
Text="Tap to start the game..."></TextBlock>
</Grid>
<!-- Game Paused Grid -->
<Grid x:Name="GamePausedGrid"
Canvas.ZIndex="3"
Width="1280"
Height="853"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,20"
Background="#AA121212"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Paused}">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<TextBlock x:Name="PausedMovesTB"
HorizontalAlignment="Center"
TextWrapping="Wrap"
FontSize="72"
Foreground="Red"
FontFamily="Segoe UI Light"
FontWeight="ExtraLight"
Margin="0,10"
Text="Game Paused!"></TextBlock>
</StackPanel>
<Button x:Name="MainResumeGameBtn"
Grid.Row="1"
Width="120"
Height="120"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="0,50,0,0"
FontSize="64"
Content=""
Style="{StaticResource MetroButton}"
Click="OnResumeGame">
</Button>
</Grid>
<Grid x:Name="GameOverGrid"
Canvas.ZIndex="3"
Width="1280"
Height="853"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="0,0,0,20"
Background="#88202020"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=GameOver}"
PointerPressed="OnPlayNextLevel">
<Grid Width="750"
Height="500"
Background="#AA121212"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="1.2*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="0.75*"></RowDefinition>
<RowDefinition Height="0.5*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="76"
Foreground="LawnGreen"
FontFamily="Segoe UI Light"
FontWeight="Light"
Margin="0"
Text="Level Complete!"></TextBlock>
<Grid Grid.RowSpan="2"
Width="280"
Height="140"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Playing}">
<Image Source="../Assets/Icons/LevelStatus/NoHStar.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=OneStar}">
<Image Source="../Assets/Icons/LevelStatus/OneHStar.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=TwoStars}">
<Image Source="../Assets/Icons/LevelStatus/TwoHStars.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=ThreeStars}">
<Image Source="../Assets/Icons/LevelStatus/ThreeHStars.png"
Stretch="Fill"></Image>
</Border>
</Grid>
<TextBlock Grid.Row="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="48"
Foreground="Yellow"
FontFamily="Segoe UI Light"
FontWeight="Light"
Margin="0"
Text="Challenge successfully completed!"
Visibility="{Binding GameOverStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=ChallengeComplete}"></TextBlock>
<TextBlock Grid.Row="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="48"
Foreground="Yellow"
FontFamily="Segoe UI Light"
FontWeight="Light"
Margin="0"
Text="Next Challenge Unlocked!"
Visibility="{Binding GameOverStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=NextChallengeUnlocked}"></TextBlock>
<TextBlock Grid.Row="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="32"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="ExtraLight"
Margin="0"
Text="Tap to play the next level..."
Visibility="{Binding GameOverStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=LevelComplete|NextChallengeUnlocked|LevelCompletePostNCUnlock}"></TextBlock>
<TextBlock Grid.Row="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="32"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="ExtraLight"
Margin="0"
Text="Tap to start the next Challenge..."
Visibility="{Binding GameOverStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=ChallengeComplete}"></TextBlock>
<TextBlock Grid.Row="4"
HorizontalAlignment="Center"
VerticalAlignment="Top"
TextWrapping="Wrap"
FontSize="24"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="ExtraLight"
Margin="0"
Text="Or press Back button to access the next Challenge..."
Visibility="{Binding GameOverStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=NextChallengeUnlocked|LevelCompletePostNCUnlock}"></TextBlock>
</Grid>
</Grid>
<!-- Practice Game Over Grid -->
<Grid x:Name="PracticeGameOverGrid"
Canvas.ZIndex="3"
Width="1280"
Height="853"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,20"
Background="#88202020"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=PracticeGameOver}"
PointerPressed="OnPracticeGameOver">
<Grid Width="700"
Height="500"
Background="#AA121212"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="80"
Foreground="LawnGreen"
FontFamily="Segoe UI Light"
FontWeight="Light"
Margin="25,10"
TextAlignment="Center"
Text="Practice Complete!"></TextBlock>
<Grid Width="280"
Height="140"
Margin="10"
HorizontalAlignment="Center">
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Playing}">
<Image Source="../Assets/Icons/LevelStatus/NoHStar.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=OneStar}">
<Image Source="../Assets/Icons/LevelStatus/OneHStar.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=TwoStars}">
<Image Source="../Assets/Icons/LevelStatus/TwoHStars.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=ThreeStars}">
<Image Source="../Assets/Icons/LevelStatus/ThreeHStars.png"
Stretch="Fill"></Image>
</Border>
</Grid>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="32"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="ExtraLight"
Margin="25,30"
Text="Tap to continue..."></TextBlock>
</StackPanel>
</Grid>
</Grid>
<!-- Custom Game Over Grid -->
<Grid x:Name="CustomGameOverGrid"
Canvas.ZIndex="3"
Width="1280"
Height="853"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,20"
Background="#88202020"
Visibility="{Binding GameStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=CustomGameOver}"
PointerPressed="OnPracticeGameOver">
<Grid Width="700"
Height="500"
Background="#AA121212"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="80"
Foreground="LawnGreen"
FontFamily="Segoe UI Light"
FontWeight="Light"
Margin="25,10"
TextAlignment="Center"
Text="Game Over!"></TextBlock>
<Grid Width="280"
Height="140"
Margin="10"
HorizontalAlignment="Center">
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=Playing}">
<Image Source="../Assets/Icons/LevelStatus/NoHStar.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=OneStar}">
<Image Source="../Assets/Icons/LevelStatus/OneHStar.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=TwoStars}">
<Image Source="../Assets/Icons/LevelStatus/TwoHStars.png"
Stretch="Fill"></Image>
</Border>
<Border Visibility="{Binding LevelStatus, Converter={StaticResource ContentVisibilityHelper}, ConverterParameter=ThreeStars}">
<Image Source="../Assets/Icons/LevelStatus/ThreeHStars.png"
Stretch="Fill"></Image>
</Border>
</Grid>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
TextWrapping="Wrap"
FontSize="32"
Foreground="White"
FontFamily="Segoe UI Light"
FontWeight="ExtraLight"
Margin="25,30"
Text="Tap to continue..."></TextBlock>
</StackPanel>
</Grid>
</Grid>
<!-- Pattern Grid -->
<Grid x:Name="PatternGrid"
Width="1280"
Height="853"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,20"
Background="Transparent"
IsHitTestVisible="False"
Visibility="{Binding IsPatternVisible, Converter={StaticResource BoolToVisibilityHelper}}"></Grid>
</Grid>
</Viewbox>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape" />
<VisualState x:Name="Filled" />
<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource PortraitBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="KeepersGrid"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="0,150,10,-150" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ChallengeImage"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="0,160,10,-160" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- The back button and title have different styles when snapped -->
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource SnappedBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="250" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="125" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="0,10,0,-10" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="KeepersGrid"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="0,150,10,-150" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ChallengeImage"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="10,160,-10,-160" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ChallengeImage"
Storyboard.TargetProperty="HorizontalAlignment">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Left" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</common:LayoutAwarePage>
/// <summary>
/// A basic page that provides characteristics common to most applications.
/// </summary>
public sealed partial class GamePage : LayoutAwarePage
{
#region Fields
DispatcherTimer _gameTimer = null;
long _totalDuration = 0;
long _totalMoves = 0;
MessageDialog _confirmQuitGameDlg = null;
MessageDialog _confirmResetGameDlg = null;
object _syncObject = new object();
string currentUniqueId = string.Empty;
bool _isPracticeGame = false;
bool _isCustomFloTileGame = false;
PatternType _customPattern = PatternType.None;
GameLevelType _customGameLevel = GameLevelType.None;
#endregion
#region Construction / Initialization
public GamePage()
{
InitializeComponent();
_gameTimer = new DispatcherTimer();
_gameTimer.Interval = TimeSpan.FromSeconds(1);
_gameTimer.Tick += UpdateTimeKeeper;
gameBoard.InitializationCompleted += OnInitializationCompleted;
gameBoard.TileMoved += OnTileMoved;
gameBoard.GameOver += OnGameOver;
applauseSound.Stop();
applauseSound.Source = new Uri("ms-appx:/Assets/Sounds/Applause.mp3");
CreateConfirmationDialogs();
}
#endregion
#region Overrides
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
/// <param name="gameState">Common Game State values</param>
async protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState, GameStateViewModel gameState)
{
currentUniqueId = navigationParameter as string;
if (!String.IsNullOrWhiteSpace(currentUniqueId))
{
_isCustomFloTileGame = LevelHelper.IsCustomFloTileGame(currentUniqueId);
if (_isCustomFloTileGame)
{
await LoadCustomFloTileGameState(pageState);
}
else
{
await LoadGameState(pageState, gameState);
}
}
}
private async Task LoadCustomFloTileGameState(Dictionary<String, Object> pageState)
{
string[] tokens = currentUniqueId.Split(new string[] { Constants.DEFAULT_SEPARATOR }, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Count() == 4)
{
_customPattern = (PatternType)Enum.Parse(typeof(PatternType), tokens[1]);
_customGameLevel = (GameLevelType)Enum.Parse(typeof(GameLevelType), tokens[2]);
int row, col;
GameStatusType savedGameStatus = GameStatusType.None;
string randomImageFilePath = string.Empty;
LevelHelper.GetLevelDetails(_customGameLevel, out row, out col, out _isPracticeGame);
bool isRestoring = false;
SerializedGameTile[] serData = null;
DefaultViewModel["ChallengeImage"] = new BitmapImage(new Uri("ms-appx:///Assets/Icons/Patterns/MyFloTiles.png"));
DefaultViewModel["GameOverStatus"] = GameOverType.None;
if (pageState != null)
{
savedGameStatus = (GameStatusType)pageState["GameStatus"];
DefaultViewModel["GameStatus"] = GameStatusType.Loading;
DefaultViewModel["TotalMoves"] = _totalDuration = (long)pageState["TotalDuration"];
DefaultViewModel["TotalDuration"] = _totalDuration = (long)pageState["TotalMoves"];
DefaultViewModel["IsPatternVisible"] = pageState["IsPatternVisible"];
DefaultViewModel["LevelStatus"] = (LevelStatusType)pageState["LevelStatus"];
randomImageFilePath = pageState["GameImagePath"] as string;
isRestoring = true;
serData = pageState["SerializedGameBoard"] as SerializedGameTile[];
ImageRandomizer.Deserialize(pageState["CachedImages"] as int[]);
}
else
{
DefaultViewModel["GameStatus"] = GameStatusType.Initializing;
DefaultViewModel["TotalMoves"] = _totalDuration = 0;
DefaultViewModel["TotalDuration"] = _totalDuration = 0;
DefaultViewModel["IsPatternVisible"] = false;
DefaultViewModel["LevelStatus"] = LevelStatusType.None;
randomImageFilePath = tokens[3];
}
DefaultViewModel["GameImagePath"] = randomImageFilePath;
StorageFile file = null;
StorageFolder imageFolder = await ApplicationData.Current.LocalFolder.GetFolderAsync("ImageBackup");
if (imageFolder != null)
{
file = await imageFolder.GetFileAsync(randomImageFilePath);
}
await gameBoard.InitializeAsync(file, _customPattern, row, col, _isPracticeGame, isRestoring, serData);
// Initialize the Pattern Grid
await InitializePatternGridAsync(_customPattern, row, col);
if (savedGameStatus != GameStatusType.None)
{
DefaultViewModel["GameStatus"] = savedGameStatus;
if ((savedGameStatus == GameStatusType.Playing) && (!_gameTimer.IsEnabled))
_gameTimer.Start();
}
}
}
private async Task LoadGameState(Dictionary<String, Object> pageState, GameStateViewModel gameState)
{
var submenuItem = gameState.GetSubMenuItem(currentUniqueId);
if (submenuItem != null)
{
string randomImageFilePath = string.Empty;
GameStatusType savedGameStatus = GameStatusType.None;
int row, col;
LevelHelper.GetLevelDetails(submenuItem.Info.Level, out row, out col, out _isPracticeGame);
bool isRestoring = false;
SerializedGameTile[] serData = null;
DefaultViewModel["ChallengeImage"] = submenuItem.Menu.IconImage;
if (pageState != null)
{
savedGameStatus = (GameStatusType)pageState["GameStatus"];
DefaultViewModel["GameStatus"] = GameStatusType.Loading;
DefaultViewModel["TotalMoves"] = _totalDuration = (long)pageState["TotalDuration"];
DefaultViewModel["TotalDuration"] = _totalDuration = (long)pageState["TotalMoves"];
DefaultViewModel["IsPatternVisible"] = pageState["IsPatternVisible"];
DefaultViewModel["LevelStatus"] = (LevelStatusType)pageState["LevelStatus"];
randomImageFilePath = pageState["GameImagePath"] as string;
isRestoring = true;
serData = pageState["SerializedGameBoard"] as SerializedGameTile[];
ImageRandomizer.Deserialize(pageState["CachedImages"] as int[]);
DefaultViewModel["GameOverStatus"] = pageState["GameOverStatus"];
}
else
{
if (!_isPracticeGame)
{
randomImageFilePath = ImageRandomizer.NextImage();
}
DefaultViewModel["GameStatus"] = GameStatusType.Initializing;
DefaultViewModel["TotalMoves"] = _totalDuration = 0;
DefaultViewModel["TotalDuration"] = _totalDuration = 0;
DefaultViewModel["IsPatternVisible"] = false;
DefaultViewModel["LevelStatus"] = submenuItem.Status;
DefaultViewModel["GameOverStatus"] = GameOverType.None;
}
DefaultViewModel["GameImagePath"] = randomImageFilePath;
StorageFile file = null;
if (!_isPracticeGame)
{
file = await Package.Current.InstalledLocation.GetFileAsync(randomImageFilePath);
if (submenuItem.Status == LevelStatusType.Locked)
{
submenuItem.Status = LevelStatusType.Playing;
await SuspensionManager.SaveAsync();
}
}
await gameBoard.InitializeAsync(file, submenuItem.Info.Pattern, row, col, _isPracticeGame, isRestoring, serData);
// Initialize the Pattern Grid
await InitializePatternGridAsync(submenuItem.Info.Pattern, row, col);
if (savedGameStatus != GameStatusType.None)
{
DefaultViewModel["GameStatus"] = savedGameStatus;
if ((savedGameStatus == GameStatusType.Playing) && (!_gameTimer.IsEnabled))
_gameTimer.Start();
}
}
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
protected override void SaveState(Dictionary<String, Object> pageState)
{
pageState["GameStatus"] = DefaultViewModel["GameStatus"];
pageState["TotalDuration"] = DefaultViewModel["TotalDuration"];
pageState["TotalMoves"] = DefaultViewModel["TotalMoves"];
pageState["IsPatternVisible"] = DefaultViewModel["IsPatternVisible"];
pageState["GameImagePath"] = DefaultViewModel["GameImagePath"];
pageState["SerializedGameBoard"] = gameBoard.GetSuspendedState();
pageState["LevelStatus"] = DefaultViewModel["LevelStatus"];
pageState["CachedImages"] = ImageRandomizer.Serialize();
pageState["GameOverStatus"] = DefaultViewModel["GameOverStatus"];
base.SaveState(pageState);
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
applauseSound.Stop();
base.OnNavigatingFrom(e);
}
#endregion
#region Event Handlers
void OnInitializationCompleted(object sender, EventArgs e)
{
DefaultViewModel["GameStatus"] = GameStatusType.TapToPlay;
}
void OnTileMoved(object sender, EventArgs e)
{
DefaultViewModel["TotalMoves"] = ++_totalMoves;
}
async void OnGameOver(object sender, EventArgs e)
{
if (_gameTimer.IsEnabled)
_gameTimer.Stop();
applauseSound.Play();
int row, col;
bool isPractice;
if (_isCustomFloTileGame)
{
DefaultViewModel["GameStatus"] = GameStatusType.CustomGameOver;
LevelHelper.GetLevelDetails(_customGameLevel, out row, out col, out isPractice);
var status = LevelHelper.GetRating(_customPattern, row, col, _totalMoves, _totalDuration);
DefaultViewModel["LevelStatus"] = status;
return;
}
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
if (frameState.ContainsKey("GameState"))
{
GameStateViewModel gVM = frameState["GameState"] as GameStateViewModel;
var submenuItem = gVM.GetSubMenuItem(currentUniqueId);
if (submenuItem != null)
{
LevelHelper.GetLevelDetails(submenuItem.Info.Level, out row, out col, out isPractice);
var status = LevelHelper.GetRating(submenuItem.Info.Pattern, row, col, _totalMoves, _totalDuration);
DefaultViewModel["LevelStatus"] = status;
submenuItem.TotalMoves = _totalMoves.ToString();
submenuItem.TotalDuration = _totalDuration.ToString();
if (_isPracticeGame)
{
DefaultViewModel["GameStatus"] = GameStatusType.PracticeGameOver;
}
else
{
submenuItem.Status = status;
DefaultViewModel["GameOverStatus"] = LevelHelper.GetGameOverStatus(currentUniqueId);
DefaultViewModel["GameStatus"] = GameStatusType.GameOver;
// Unlock the next level in the challenge
string nextLevelId = LevelHelper.GetNextLevel(currentUniqueId);
if (!String.IsNullOrWhiteSpace(nextLevelId))
{
var nextSubMenuItem = gVM.GetSubMenuItem(nextLevelId);
if (nextSubMenuItem != null)
nextSubMenuItem.Status = LevelStatusType.Playing;
}
// If the currently completed level is the Boundary level (5th level), then
// unlock the next challenge's first level.
if (LevelHelper.IsBoundaryLevel(currentUniqueId))
{
string nextChallengeLevelId = LevelHelper.GetNextChallengeLevel(currentUniqueId);
if (!String.IsNullOrWhiteSpace(nextChallengeLevelId))
{
var nextChallengeLevel = gVM.GetSubMenuItem(nextChallengeLevelId);
if (nextChallengeLevel != null)
nextChallengeLevel.Status = LevelStatusType.Playing;
}
}
}
await SuspensionManager.SaveAsync();
}
}
}
async private void GoBackInternal(object sender, RoutedEventArgs e)
{
if (((GameStatusType)DefaultViewModel["GameStatus"] != GameStatusType.GameOver) &&
((GameStatusType)DefaultViewModel["GameStatus"] != GameStatusType.PracticeGameOver))
{
var result = await ConfirmQuitGameAsync();
if (result.Label == "Yes")
base.GoBack(this, new RoutedEventArgs());
}
else
{
base.GoBack(this, new RoutedEventArgs());
}
}
void UpdateTimeKeeper(object sender, object e)
{
lock (_syncObject)
{
DefaultViewModel["TotalDuration"] = ++_totalDuration;
}
}
void OnTapToStart(object sender, PointerRoutedEventArgs e)
{
DefaultViewModel["GameStatus"] = GameStatusType.Playing;
ResetKeepers();
_gameTimer.Start();
}
void OnPauseGame(object sender, RoutedEventArgs e)
{
if (_gameTimer.IsEnabled)
_gameTimer.Stop();
DefaultViewModel["GameStatus"] = GameStatusType.Paused;
}
void OnResumeGame(object sender, RoutedEventArgs e)
{
DefaultViewModel["GameStatus"] = GameStatusType.Playing;
_gameTimer.Start();
}
void OnPlayNextLevel(object sender, PointerRoutedEventArgs e)
{
applauseSound.Stop();
if (_isCustomFloTileGame)
{
base.GoBack(this, new RoutedEventArgs());
}
else
{
string nextLevelId = LevelHelper.GetNextLevel(currentUniqueId);
if (!String.IsNullOrWhiteSpace(nextLevelId))
{
GoBack(this, new RoutedEventArgs());
this.Frame.Navigate(typeof(GamePage), nextLevelId);
}
}
}
private void OnPracticeGameOver(object sender, PointerRoutedEventArgs e)
{
base.GoBack(this, new RoutedEventArgs());
}
async void OnResetGame(object sender, RoutedEventArgs e)
{
await ConfirmResetGameAsync();
}
void OnShowPattern(object sender, RoutedEventArgs e)
{
lock (_syncObject)
{
_totalDuration += 30;
DefaultViewModel["TotalDuration"] = _totalDuration;
}
DefaultViewModel["IsPatternVisible"] = true;
}
void OnHidePattern(object sender, RoutedEventArgs e)
{
DefaultViewModel["IsPatternVisible"] = false;
}
void OnSolve(object sender, RoutedEventArgs e)
{
lock (_syncObject)
{
_totalDuration += 20;
DefaultViewModel["TotalDuration"] = _totalDuration;
}
DefaultViewModel["GameStatus"] = GameStatusType.Solved;
gameBoard.SolveGame();
}
void OnContinueGaming(object sender, RoutedEventArgs e)
{
gameBoard.ResumeGame();
DefaultViewModel["GameStatus"] = GameStatusType.Playing;
}
#endregion
#region Helpers
void CreateConfirmationDialogs()
{
_confirmQuitGameDlg = new MessageDialog("Do you want to discard the current game?", "FloTiles");
_confirmQuitGameDlg.Commands.Add(new UICommand("Yes"));
_confirmQuitGameDlg.Commands.Add(new UICommand("No", async (x) =>
{
await gameBoard.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if ((GameStatusType)DefaultViewModel["GameStatus"] == GameStatusType.Playing)
_gameTimer.Start();
});
}));
_confirmQuitGameDlg.DefaultCommandIndex = 1;
_confirmQuitGameDlg.CancelCommandIndex = 1;
_confirmResetGameDlg = new MessageDialog("Are you sure you want to reset the current game?", "FloTiles");
_confirmResetGameDlg.Commands.Add(new UICommand("Yes", (x) =>
{
ResetTheGame();
}));
_confirmResetGameDlg.Commands.Add(new UICommand("No", async (x) =>
{
await gameBoard.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
if ((GameStatusType)DefaultViewModel["GameStatus"] == GameStatusType.Playing)
_gameTimer.Start();
});
}));
_confirmResetGameDlg.DefaultCommandIndex = 1;
_confirmResetGameDlg.CancelCommandIndex = 1;
}
async Task<IUICommand> ConfirmQuitGameAsync()
{
if (_gameTimer.IsEnabled)
_gameTimer.Stop();
return await _confirmQuitGameDlg.ShowAsync();
}
async Task ConfirmResetGameAsync()
{
_gameTimer.Stop();
await _confirmResetGameDlg.ShowAsync();
}
void ResetTheGame()
{
DefaultViewModel["GameStatus"] = GameStatusType.Initializing;
if (_gameTimer.IsEnabled)
_gameTimer.Stop();
ResetKeepers();
gameBoard.ResetGame();
DefaultViewModel["GameStatus"] = GameStatusType.TapToPlay;
}
void ResetKeepers()
{
DefaultViewModel["TotalMoves"] = _totalDuration = 0;
DefaultViewModel["TotalDuration"] = _totalDuration = 0;
}
async Task InitializePatternGridAsync(PatternType pattern, int maxRows, int maxCols)
{
await gameBoard.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
PatternGrid.RowDefinitions.Clear();
PatternGrid.ColumnDefinitions.Clear();
PatternGrid.Children.Clear();
Dictionary<int, Cell> cellIndex = IndexGenerator.GenerateIndex(maxRows, maxCols, pattern);
if (cellIndex == null)
return;
for (int i = 0; i < maxRows; i++)
{
RowDefinition rd = new RowDefinition();
rd.Height = new GridLength(1, GridUnitType.Star);
PatternGrid.RowDefinitions.Add(rd);
}
for (int i = 0; i < maxCols; i++)
{
ColumnDefinition cd = new ColumnDefinition();
cd.Width = new GridLength(1, GridUnitType.Star);
PatternGrid.ColumnDefinitions.Add(cd);
}
for (int i = 0; i < maxRows * maxCols; i++)
{
PatternBadge badge = new PatternBadge { Text = (i + 1).ToString(), Width = 50, Height = 50, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top };
Cell c = cellIndex[i];
Grid.SetRow(badge, c.Row);
Grid.SetColumn(badge, c.Column);
PatternGrid.Children.Add(badge);
//
}
});
}
#endregion
}
ScoreboardPage
此页面负责显示到目前为止已玩的Challenges
和Levels
的统计信息。此页面还支持纵向和贴靠状态。
代码
<common:LayoutAwarePage x:Name="pageRoot"
x:Class="FloTiles.Views.ScoreBoardPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FloTiles.Views"
xmlns:common="using:FloTiles.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<!-- Collection of items displayed by this page -->
<CollectionViewSource x:Name="itemsViewSource"
Source="{Binding MenuItems}" />
<CollectionViewSource x:Name="snappedItemsViewSource"
Source="{Binding MenuItems}"
IsSourceGrouped="true"
ItemsPath="SubMenuItems" />
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!--<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="primaryColumn"
Width="500" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>-->
<!-- Background -->
<Grid.Background>
<ImageBrush ImageSource="../Assets/Images/Background/BG06.jpg"
Stretch="UniformToFill"
AlignmentX="Left"
AlignmentY="Top"></ImageBrush>
</Grid.Background>
<!-- Back button and Title Logo -->
<Grid x:Name="titlePanel"
Grid.ColumnSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton"
Click="GoBack"
IsEnabled="{Binding DefaultViewModel.CanGoBack, ElementName=pageRoot}"
Style="{StaticResource BackButtonStyle}" />
<Image x:Name="TitleLogo"
Grid.Column="1"
Source="../Assets/Icons/FloTilesHeader.png"
Stretch="Uniform"
Width="280"
Height="140"
Margin="-20,0,20,0"
IsHitTestVisible="False"
HorizontalAlignment="Left" />
</Grid>
<!-- View shown when the not snapped -->
<Viewbox x:Name="UnsnappedView"
Margin="10,0,-10,0"
Grid.Row="1"
Grid.ColumnSpan="2"
MaxWidth="1250"
MaxHeight="700"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Grid Width="1250"
Height="700"
Margin="20,0"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="450"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="750"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!-- Vertical scrolling item list -->
<ListBox x:Name="LandscapeListView"
AutomationProperties.AutomationId="ItemsListView"
AutomationProperties.Name="Items"
Width="450"
Height="660"
Grid.Row="1"
VerticalAlignment="Top"
FontFamily="Segoe UI Light"
FontWeight="Light"
FontSize="16"
Foreground="White"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Source={StaticResource itemsViewSource}}"
ItemContainerStyle="{StaticResource ScoreboardLBItem}"
SelectionChanged="ItemListView_SelectionChanged"
Style="{StaticResource ScoreboardLBStyle}"
ItemTemplate="{StaticResource ScoreBoardMenuItemTemplate}">
</ListBox>
<!-- Details for selected item -->
<Grid x:Name="DetailsGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Grid.Column="2"
Width="750"
Height="700"
Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="65"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid Height="65"
Width="750"
Background="#4B0082">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="0.75*" />
<ColumnDefinition Width="0.75*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="Level"
FontFamily="Segoe UI Light"
FontSize="24"
FontWeight="SemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
<TextBlock Grid.Column="1"
Text="Rating"
FontFamily="Segoe UI Light"
FontSize="24"
FontWeight="SemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
<TextBlock Grid.Column="2"
Text="Moves"
FontFamily="Segoe UI Light"
FontSize="24"
Margin="0,0,0,0"
FontWeight="SemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
<TextBlock Grid.Column="3"
Text="Duration"
FontFamily="Segoe UI Light"
FontSize="24"
Margin="0,0,0,0"
FontWeight="SemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"></TextBlock>
</Grid>
<ListBox x:Name="itemDetail"
AutomationProperties.AutomationId="SubItemsListView"
AutomationProperties.Name="SubItems"
TabIndex="1"
Grid.Row="1"
Width="750"
VerticalAlignment="Top"
Margin="0,0,0,0"
Padding="0"
FontFamily="Segoe UI Light"
FontWeight="Light"
FontSize="16"
Foreground="White"
ItemContainerStyle="{StaticResource ScoreboardLBItemNR}"
ItemsSource="{Binding SelectedItem.SubMenuItems, ElementName=LandscapeListView}"
ItemTemplate="{StaticResource ScoreBoardDetailsTemplate}"
Style="{StaticResource ScoreboardLBStyle}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel></StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
<Border Grid.ColumnSpan="3"
VerticalAlignment="Top"
Margin="0,-83,0,80"
Width="350"
Height="66"
HorizontalAlignment="Center"
Background="Indigo">
<TextBlock FontFamily="Segoe UI Light"
FontSize="56"
FontWeight="Light"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,4,0,-4"
Text="Scoreboard"></TextBlock>
</Border>
</Grid>
</Viewbox>
<!-- Vertical scrolling list only used when snapped -->
<ListView x:Name="SnappedView"
AutomationProperties.AutomationId="SnappedListView"
AutomationProperties.Name="Grouped Items"
Grid.Row="1"
Visibility="Collapsed"
Margin="0,-10,0,0"
Padding="10,0,0,60"
ItemsSource="{Binding Source={StaticResource snappedItemsViewSource}}"
ItemTemplate="{StaticResource SnappedScoreboardItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="2,20,2,10">
<Border Background="Indigo"
Width="300"
Height="50">
<StackPanel Orientation="Horizontal">
<Grid Width="40"
Height="40"
Margin="4">
<Border Background="#0276FD"></Border>
<Border>
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
<Image Source="{Binding Menu.IconImage}"
Stretch="Uniform" />
</Border>
</Grid>
<TextBlock Text="{Binding Menu.Title}"
Margin="10,-4,10,10"
VerticalAlignment="Center"
Style="{StaticResource GroupHeaderTextStyleSnapped}"
TextWrapping="Wrap" />
</StackPanel>
</Border>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Margin="0,-200,0,200"
Orientation="Horizontal"
MaximumRowsOrColumns="2"
VerticalChildrenAlignment="Center" />
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape" />
<VisualState x:Name="Filled" />
<VisualState x:Name="FullScreenPortrait" />
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource SnappedBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="250" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="125" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="0,10,0,-10" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UnsnappedView"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Collapsed" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SnappedView"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Visible" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</common:LayoutAwarePage>
/// <summary>
/// A page that displays a group title, a list of items within the group, and details for
/// the currently selected item.
/// </summary>
public sealed partial class ScoreBoardPage : LayoutAwarePage
{
public ScoreBoardPage()
{
this.InitializeComponent();
}
#region Page state management
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState, GameStateViewModel gameState)
{
// TODO: Assign a bindable group to this.DefaultViewModel["Group"]
// TODO: Assign a collection of bindable items to this.DefaultViewModel["Items"]
IEnumerable<MenuItem> dataSource = (gameState != null) ? gameState.MenuItems : GameMenuSource.GetMenuItems();
var menuItems = from item in dataSource
where (item.Info.UniqueId != "PAT_11")
group item by item into g
select new GroupData { Menu = g.Key, SubMenuItems = g.Key.SubMenuItems };
this.DefaultViewModel["MenuItems"] = menuItems.ToList();
if (pageState == null)
{
// When this is a new page, select the first item automatically unless logical page
// navigation is being used (see the logical page navigation #region below.)
if (!this.UsingLogicalPageNavigation() && this.itemsViewSource.View != null)
{
this.itemsViewSource.View.MoveCurrentToFirst();
}
}
else
{
// Restore the previously saved state associated with this page
if (pageState.ContainsKey("SelectedItem") && this.itemsViewSource.View != null)
{
// TODO: Invoke this.itemsViewSource.View.MoveCurrentTo() with the selected
// item as specified by the value of pageState["SelectedItem"]
this.itemsViewSource.View.MoveCurrentTo(pageState["SelectedItem"]);
}
}
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
protected override void SaveState(Dictionary<String, Object> pageState)
{
if (this.itemsViewSource.View != null)
{
pageState["SelectedItem"] = this.itemsViewSource.View.CurrentItem;
// TODO: Derive a serializable navigation parameter and assign it to
// pageState["SelectedItem"]
}
}
#endregion
#region Logical page navigation
// Visual state management typically reflects the four application view states directly
// (full screen landscape and portrait plus snapped and filled views.) The split page is
// designed so that the snapped and portrait view states each have two distinct sub-states:
// either the item list or the details are displayed, but not both at the same time.
//
// This is all implemented with a single physical page that can represent two logical
// pages. The code below achieves this goal without making the user aware of the
// distinction.
/// <summary>
/// Invoked to determine whether the page should act as one logical page or two.
/// </summary>
/// <param name="viewState">The view state for which the question is being posed, or null
/// for the current view state. This parameter is optional with null as the default
/// value.</param>
/// <returns>True when the view state in question is portrait or snapped, false
/// otherwise.</returns>
private bool UsingLogicalPageNavigation(ApplicationViewState? viewState = null)
{
//if (viewState == null) viewState = ApplicationView.Value;
//return viewState == ApplicationViewState.FullScreenPortrait ||
// viewState == ApplicationViewState.Snapped;
return false;
}
/// <summary>
/// Invoked when an item within the list is selected.
/// </summary>
/// <param name="sender">The GridView (or ListView when the application is Snapped)
/// displaying the selected item.</param>
/// <param name="e">Event data that describes how the selection was changed.</param>
void ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Invalidate the view state when logical page navigation is in effect, as a change
// in selection may cause a corresponding change in the current logical page. When
// an item is selected this has the effect of changing from displaying the item list
// to showing the selected item's details. When the selection is cleared this has the
// opposite effect.
if (this.UsingLogicalPageNavigation()) this.InvalidateVisualState();
}
/// <summary>
/// Invoked when the page's back button is pressed.
/// </summary>
/// <param name="sender">The back button instance.</param>
/// <param name="e">Event data that describes how the back button was clicked.</param>
protected override void GoBack(object sender, RoutedEventArgs e)
{
if (this.UsingLogicalPageNavigation() && LandscapeListView.SelectedItem != null)
{
// When logical page navigation is in effect and there's a selected item that
// item's details are currently displayed. Clearing the selection will return to
// the item list. From the user's point of view this is a logical backward
// navigation.
this.LandscapeListView.SelectedItem = null;
}
else
{
// When logical page navigation is not in effect, or when there is no selected
// item, use the default back button behavior.
base.GoBack(sender, e);
}
}
/// <summary>
/// Invoked to determine the name of the visual state that corresponds to an application
/// view state.
/// </summary>
/// <param name="viewState">The view state for which the question is being posed.</param>
/// <returns>The name of the desired visual state. This is the same as the name of the
/// view state except when there is a selected item in portrait and snapped views where
/// this additional logical page is represented by adding a suffix of _Detail.</returns>
protected override string DetermineVisualState(ApplicationViewState viewState)
{
// Update the back button's enabled state when the view state changes
var logicalPageBack = this.UsingLogicalPageNavigation(viewState) && this.LandscapeListView.SelectedItem != null;
var physicalPageBack = this.Frame != null && this.Frame.CanGoBack;
this.DefaultViewModel["CanGoBack"] = logicalPageBack || physicalPageBack;
// Determine visual states for landscape layouts based not on the view state, but
// on the width of the window. This page has one layout that is appropriate for
// 1366 virtual pixels or wider, and another for narrower displays or when a snapped
// application reduces the horizontal space available to less than 1366.
if (viewState == ApplicationViewState.Filled ||
viewState == ApplicationViewState.FullScreenLandscape)
{
var windowWidth = Window.Current.Bounds.Width;
if (windowWidth >= 1366) return "FullScreenLandscape";
return "Filled";
}
// When in portrait or snapped start with the default visual state name, then add a
// suffix when viewing details instead of the list
var defaultStateName = base.DetermineVisualState(viewState);
//return logicalPageBack ? defaultStateName + "_Detail" : defaultStateName;
return defaultStateName;
}
#endregion
}
MyFloTilesPage
此页面允许用户通过使用网络摄像头或从“我的图片”文件夹中选择图片来创建自定义的FloTiles
谜题。
代码
<common:LayoutAwarePage x:Name="pageRoot"
x:Class="FloTiles.Views.MyFloTilesPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FloTiles.Views"
xmlns:common="using:FloTiles.Common"
xmlns:converters="using:FloTiles.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<Style x:Key="MetroButtonStyle"
TargetType="Button">
<Setter Property="FontFamily"
Value="Segoe UI Symbol" />
<Setter Property="FontWeight"
Value="SemiBold" />
<Setter Property="FontSize"
Value="64" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="VerticalAlignment"
Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="ButtonContent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape" />
<VisualState x:Name="Filled" />
<VisualState x:Name="FullScreenPortrait" />
<VisualState x:Name="Snapped" />
</VisualStateGroup>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonOutline"
Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)">
<DiscreteObjectKeyFrame KeyTime="0"
Value="#44FFFFFF" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonOutline"
Storyboard.TargetProperty="(Ellipse.Stroke).(SolidColorBrush.Color)">
<DiscreteObjectKeyFrame KeyTime="0"
Value="White" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonOutline"
Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)">
<DiscreteObjectKeyFrame KeyTime="0"
Value="White" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Glyph"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Black" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonOutline"
Storyboard.TargetProperty="(Ellipse.Stroke).(SolidColorBrush.Color)">
<DiscreteObjectKeyFrame KeyTime="0"
Value="#323232" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Glyph"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0"
Value="#323232" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused" />
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Ellipse x:Name="ButtonOutline"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Fill="Transparent"
HorizontalAlignment="Center"
Stroke="White"
StrokeThickness="2"
VerticalAlignment="Center" />
<TextBlock x:Name="Glyph"
Text="{TemplateBinding Content}"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Opacity="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Collection of items displayed by this page -->
<CollectionViewSource x:Name="menuItemsSource"
Source="{Binding MenuItems}"
IsSourceGrouped="False" />
<CollectionViewSource x:Name="levelSource"
Source="{Binding Levels}"
IsSourceGrouped="False" />
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHelper"></converters:BooleanToVisibilityConverter>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Background>
<ImageBrush ImageSource="../Assets/Images/Background/BG08.jpg"
Stretch="UniformToFill"
AlignmentX="Left"
AlignmentY="Top"></ImageBrush>
</Grid.Background>
<!-- Back button and Title Logo -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button x:Name="backButton"
Click="GoBack"
IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}"
Style="{StaticResource BackButtonStyle}" />
<Image x:Name="TitleLogo"
Grid.Column="1"
Source="../Assets/Icons/FloTilesHeader.png"
Stretch="Uniform"
Width="280"
Height="140"
Margin="-20,0,20,0"
IsHitTestVisible="False"
HorizontalAlignment="Left" />
</Grid>
<Grid x:Name="InnerGrid"
Grid.Row="1"
Width="1200"
Height="650"
Margin="0,-20,0,10"
Background="#EE111111">
<Grid.RowDefinitions>
<RowDefinition Height="300"></RowDefinition>
<RowDefinition Height="200"></RowDefinition>
<RowDefinition Height="150"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="Header"
Grid.Row="0"
Text="Create FloTile"
FontSize="56"
FontFamily="Segoe UI Light"
FontWeight="Light"
Foreground="#9A9A9A"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10,5"></TextBlock>
<Button x:Name="SelectImageBtn"
Style="{StaticResource TextPrimaryButtonStyle}"
Width="150"
Height="190"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="OnSelectImage"
PointerEntered="OnPointerEntered"
PointerExited="OnPointerExited">
<Grid>
<Border Background="#0276FD"
Width="150"
Height="150"
VerticalAlignment="Top"></Border>
<Border Width="150"
Height="150"
VerticalAlignment="Top">
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Image Source="../Assets/Icons/Levels/MyPictures.png"
Stretch="Uniform"
Width="150"
Height="150"
VerticalAlignment="Top" />
<Border Height="40"
VerticalAlignment="Bottom"
Background="#646464"></Border>
<TextBlock Text="Select Image"
Margin="0,-10,0,10"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
FontSize="22"
Style="{StaticResource GroupHeaderTextStyle}" />
</Grid>
</Button>
<Button x:Name="CapturePhotoBtn"
Style="{StaticResource TextPrimaryButtonStyle}"
Width="150"
Height="190"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="OnCapturePhoto"
PointerEntered="OnPointerEntered"
PointerExited="OnPointerExited"
Visibility="{Binding IsCameraView, Converter={StaticResource BoolToVisibilityHelper}}">
<Grid>
<Border Background="#0276FD"
Width="150"
Height="150"
VerticalAlignment="Top"></Border>
<Border Width="150"
Height="150"
VerticalAlignment="Top">
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Image Source="../Assets/Icons/Levels/Camera.png"
Stretch="Uniform"
Width="150"
Height="150"
VerticalAlignment="Top" />
<Border Height="40"
VerticalAlignment="Bottom"
Background="#646464"></Border>
<TextBlock Text="Capture Photo"
Margin="0,-10,0,10"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
FontSize="22"
Style="{StaticResource GroupHeaderTextStyle}" />
</Grid>
</Button>
<Image x:Name="ImgPreview"
Width="400"
Height="280"
Stretch="Fill"
Source="{Binding SelectedImage}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="10,0"></Image>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.38*"></ColumnDefinition>
<ColumnDefinition Width="10"></ColumnDefinition>
<ColumnDefinition Width="0.62*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Challenge Type"
FontSize="22"
FontFamily="Segoe UI Light"
FontWeight="Light"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="10,0"></TextBlock>
<ComboBox x:Name="ChallengeCB"
Grid.Row="0"
Grid.Column="2"
Width="300"
Height="34"
HorizontalAlignment="Left"
VerticalAlignment="Center"
ItemsSource="{Binding Source={StaticResource menuItemsSource}}"
SelectionChanged="OnChallengeSelected">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Menu.Title}"
HorizontalAlignment="Left"
VerticalAlignment="Center"></TextBlock>
<Image Grid.Column="1"
Width="30"
Height="30"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Source="../Assets/Icons/LevelStatus/QLock.png"
Stretch="Uniform"
ToolTipService.ToolTip="Level is Locked!"
Visibility="{Binding Menu.IsLocked, Converter={StaticResource BoolToVisibilityHelper}}"></Image>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="1"
Text="Challenge Level"
FontSize="22"
FontFamily="Segoe UI Light"
FontWeight="Light"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="10,0"></TextBlock>
<ComboBox x:Name="LevelCB"
Grid.Row="1"
Grid.Column="2"
Width="300"
Height="34"
ItemsSource="{Binding Source={StaticResource levelSource}}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
SelectionChanged="OnLevelSelected"></ComboBox>
</Grid>
<Button Grid.Row="2"
Width="120"
Height="120"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="10"
FontSize="56"
Content=""
IsEnabled="{Binding CanCreateFloTile}"
Style="{StaticResource MetroButtonStyle}"
Click="OnCreateFloTile"
ToolTipService.ToolTip="Create FloTile">
</Button>
</Grid>
<!-- Grid for Snapped State -->
<Grid x:Name="SnappedGrid"
Grid.Row="1"
Width="330"
Height="660"
Margin="0,0,0,0"
Background="#EE111111"
Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="240"></RowDefinition>
<RowDefinition Height="170"></RowDefinition>
<RowDefinition Height="75"></RowDefinition>
<RowDefinition Height="75"></RowDefinition>
<RowDefinition Height="90"></RowDefinition>
</Grid.RowDefinitions>
<Image x:Name="SnappedImgPreview"
Width="320"
Height="220"
Stretch="Fill"
Source="{Binding SelectedImage}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0"></Image>
<Button x:Name="SnappedSelectImageBtn"
Style="{StaticResource TextPrimaryButtonStyle}"
Grid.Row="1"
Width="120"
Height="152"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="OnSelectImage"
PointerEntered="OnPointerEntered"
PointerExited="OnPointerExited">
<Grid>
<Border Background="#0276FD"
Width="120"
Height="120"
VerticalAlignment="Top"></Border>
<Border Width="120"
Height="120"
VerticalAlignment="Top">
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Image Source="../Assets/Icons/Levels/MyPictures.png"
Stretch="Uniform"
Width="120"
Height="120"
VerticalAlignment="Top" />
<Border Height="32"
VerticalAlignment="Bottom"
Background="#646464"></Border>
<TextBlock Text="Select Image"
Margin="0,-10,0,10"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
FontSize="16"
Style="{StaticResource GroupHeaderTextStyle}" />
</Grid>
</Button>
<Button x:Name="SnappedCapturePhotoBtn"
Style="{StaticResource TextPrimaryButtonStyle}"
Grid.Row="1"
Width="120"
Height="152"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Click="OnCapturePhoto"
PointerEntered="OnPointerEntered"
PointerExited="OnPointerExited"
Visibility="{Binding IsCameraView, Converter={StaticResource BoolToVisibilityHelper}}">
<Grid>
<Border Background="#0276FD"
Width="120"
Height="120"
VerticalAlignment="Top"></Border>
<Border Width="120"
Height="120"
VerticalAlignment="Top">
<Border.Background>
<LinearGradientBrush EndPoint="1.436,1.406"
StartPoint="0.552,0.456">
<GradientStop Color="#00FFFFFF"
Offset="0.081" />
<GradientStop Color="White"
Offset="0.912" />
<GradientStop Color="#00FFFFFF"
Offset="0.011" />
</LinearGradientBrush>
</Border.Background>
</Border>
<Image Source="../Assets/Icons/Levels/Camera.png"
Stretch="Uniform"
Width="120"
Height="120"
VerticalAlignment="Top" />
<Border Height="32"
VerticalAlignment="Bottom"
Background="#646464"></Border>
<TextBlock Text="Capture Photo"
Margin="0,-10,0,10"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
FontSize="16"
Style="{StaticResource GroupHeaderTextStyle}" />
</Grid>
</Button>
<TextBlock Text="Challenge Type"
Grid.Row="2"
FontSize="16"
FontFamily="Segoe UI Light"
FontWeight="Light"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5,0"></TextBlock>
<ComboBox x:Name="SnappedChallengeCB"
Grid.Row="2"
Width="300"
Height="34"
Margin="-5,2,5,-2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ItemsSource="{Binding Source={StaticResource menuItemsSource}}"
SelectionChanged="OnSnappedChallengeSelected">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Menu.Title}"
HorizontalAlignment="Left"
VerticalAlignment="Center"></TextBlock>
<Image Grid.Column="1"
Width="30"
Height="30"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Source="../Assets/Icons/LevelStatus/QLock.png"
Stretch="Uniform"
ToolTipService.ToolTip="Level is Locked!"
Visibility="{Binding Menu.IsLocked, Converter={StaticResource BoolToVisibilityHelper}}"></Image>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="3"
Text="Challenge Level"
FontSize="16"
FontFamily="Segoe UI Light"
FontWeight="Light"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5,0"></TextBlock>
<ComboBox x:Name="SnappedLevelCB"
Grid.Row="3"
Width="300"
Height="34"
Margin="-5,2,5,-2"
ItemsSource="{Binding Source={StaticResource levelSource}}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
SelectionChanged="OnSnappedLevelSelected"></ComboBox>
<Button Grid.Row="4"
Width="60"
Height="60"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Margin="0,-5,0,5"
FontSize="36"
Content=""
IsEnabled="{Binding CanCreateFloTile}"
Style="{StaticResource MetroButtonStyle}"
Click="OnCreateFloTile"
ToolTipService.ToolTip="Create FloTile">
</Button>
</Grid>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape" />
<VisualState x:Name="Filled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InnerGrid"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="950" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ImgPreview"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="350" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ImgPreview"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="245" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource PortraitBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InnerGrid"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="750" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Header"
Storyboard.TargetProperty="FontSize">
<DiscreteObjectKeyFrame KeyTime="0"
Value="36" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ImgPreview"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="280" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ImgPreview"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="196" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ImgPreview"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="10,-10,10,10" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!-- The back button and title have different styles when snapped -->
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton"
Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{StaticResource SnappedBackButtonStyle}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0"
Value="250" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Height">
<DiscreteObjectKeyFrame KeyTime="0"
Value="125" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TitleLogo"
Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0"
Value="0,10,0,-10" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InnerGrid"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Collapsed" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SnappedGrid"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0"
Value="Visible" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</common:LayoutAwarePage>
/// <summary>
/// A basic page that provides characteristics common to most applications.
/// </summary>
public sealed partial class MyFloTilesPage : FloTiles.Common.LayoutAwarePage
{
#region Fields
GroupData _selectedChallenge = null;
string _selectedLevel = string.Empty;
#endregion
public MyFloTilesPage()
{
this.InitializeComponent();
}
/// <summary>
/// Populates the page with content passed during navigation. Any saved state is also
/// provided when recreating a page from a prior session.
/// </summary>
/// <param name="navigationParameter">The parameter value passed to
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested.
/// </param>
/// <param name="pageState">A dictionary of state preserved by this page during an earlier
/// session. This will be null the first time a page is visited.</param>
async protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState, GameStateViewModel gameState)
{
string uniqueId = navigationParameter as string;
DefaultViewModel["IsCameraView"] = uniqueId.Equals("MFT_CAM");
IEnumerable<MenuItem> dataSource = (gameState != null) ? gameState.MenuItems : GameMenuSource.GetMenuItems();
var menuItems = from item in dataSource
where (item.Info.UniqueId != "PAT_11")
group item by item into g
select new GroupData { Menu = g.Key, SubMenuItems = g.Key.SubMenuItems };
this.DefaultViewModel["MenuItems"] = menuItems.ToList();
this.DefaultViewModel["Levels"] = new List<string> {
"Level 1",
"Level 2",
"Level 3",
"Level 4",
"Level 5",
"Level 6",
"Level 7",
"Level 8",
};
if (pageState != null)
{
string imgName = pageState["SelectedImageName"] as string;
StorageFolder imageBackupFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("ImageBackup", CreationCollisionOption.OpenIfExists);
StorageFile file = await imageBackupFolder.GetFileAsync(imgName);
if (file != null)
{
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read))
{
BitmapImage bitmap = new BitmapImage();
stream.Seek(0);
bitmap.SetSource(stream);
if (bitmap != null)
{
DefaultViewModel["SelectedImage"] = bitmap;
DefaultViewModel["SelectedImageName"] = file.Name;
}
}
}
SnappedChallengeCB.SelectedIndex = ChallengeCB.SelectedIndex = (int)pageState["ChallengeType"];
SnappedLevelCB.SelectedIndex = LevelCB.SelectedIndex = (int)pageState["ChallengeLevel"];
DefaultViewModel["IsCameraView"] = (bool)pageState["IsCameraView"];
_selectedChallenge = (GroupData)ChallengeCB.SelectedItem;
_selectedLevel = (string)LevelCB.SelectedItem;
CheckCanCreateFloTile();
}
}
/// <summary>
/// Preserves state associated with this page in case the application is suspended or the
/// page is discarded from the navigation cache. Values must conform to the serialization
/// requirements of <see cref="SuspensionManager.SessionState"/>.
/// </summary>
/// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
protected override void SaveState(Dictionary<String, Object> pageState)
{
pageState["IsCameraView"] = DefaultViewModel["IsCameraView"];
pageState["SelectedImageName"] = DefaultViewModel.ContainsKey("SelectedImageName") ? DefaultViewModel["SelectedImageName"] : null;
// CHECK
pageState["ChallengeType"] = ChallengeCB.SelectedIndex;
pageState["ChallengeLevel"] = LevelCB.SelectedIndex;
}
async private void OnSelectImage(object sender, RoutedEventArgs e)
{
if (ApplicationView.Value == ApplicationViewState.Snapped)
{
bool isUnsnapped = ApplicationView.TryUnsnap();
if (!isUnsnapped)
{
MessageDialog msgDlg = new MessageDialog("Please unsnap the application to select the image!", "FloTiles");
msgDlg.Commands.Add(new UICommand("Ok"));
msgDlg.CancelCommandIndex = 0;
await msgDlg.ShowAsync();
return;
}
}
FileOpenPicker filePicker = new FileOpenPicker()
{
FileTypeFilter = { ".jpg", ".png" },
CommitButtonText = "Select",
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
ViewMode = PickerViewMode.Thumbnail
};
StorageFile file = await filePicker.PickSingleFileAsync();
if (file == null)
return;
await Task.Run(async () =>
{
StorageFolder imageBackupFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("ImageBackup", CreationCollisionOption.OpenIfExists);
foreach (var fileBak in await imageBackupFolder.GetFilesAsync())
{
await fileBak.DeleteAsync();
}
file = await file.CopyAsync(imageBackupFolder, String.Format("FloTileImage{0}", file.FileType), NameCollisionOption.ReplaceExisting);
});
if (file == null)
return;
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read))
{
BitmapImage bitmap = new BitmapImage();
stream.Seek(0);
bitmap.SetSource(stream);
if (bitmap != null)
{
DefaultViewModel["SelectedImage"] = bitmap;
DefaultViewModel["SelectedImageName"] = file.Name;
CheckCanCreateFloTile();
}
}
}
private void OnPointerEntered(object sender, PointerRoutedEventArgs e)
{
Window.Current.CoreWindow.PointerCursor = new Windows.UI.Core.CoreCursor(Windows.UI.Core.CoreCursorType.Hand, 1);
}
private void OnPointerExited(object sender, PointerRoutedEventArgs e)
{
Window.Current.CoreWindow.PointerCursor = new Windows.UI.Core.CoreCursor(Windows.UI.Core.CoreCursorType.Arrow, 0);
}
private void OnChallengeSelected(object sender, SelectionChangedEventArgs e)
{
_selectedChallenge = (GroupData)ChallengeCB.SelectedItem;
CheckCanCreateFloTile();
}
private void OnSnappedChallengeSelected(object sender, SelectionChangedEventArgs e)
{
_selectedChallenge = (GroupData)SnappedChallengeCB.SelectedItem;
CheckCanCreateFloTile();
}
private void CheckCanCreateFloTile()
{
DefaultViewModel["CanCreateFloTile"] = DefaultViewModel.ContainsKey("SelectedImage") &&
(DefaultViewModel["SelectedImage"] != null) &&
(_selectedChallenge != null) &&
(!_selectedChallenge.Menu.IsLocked) &&
(!String.IsNullOrWhiteSpace(_selectedLevel));
}
private void OnLevelSelected(object sender, SelectionChangedEventArgs e)
{
_selectedLevel = (string)LevelCB.SelectedItem;
CheckCanCreateFloTile();
}
private void OnSnappedLevelSelected(object sender, SelectionChangedEventArgs e)
{
_selectedLevel = (string)SnappedLevelCB.SelectedItem;
CheckCanCreateFloTile();
}
private void OnCreateFloTile(object sender, RoutedEventArgs e)
{
string pattern = _selectedChallenge.Menu.Info.Pattern.ToString();
string level = _selectedLevel.Replace(" ", string.Empty);
string imgPath = DefaultViewModel["SelectedImageName"] as string;
string naviParam = (bool)DefaultViewModel["IsCameraView"] ? string.Format("MFT_CAM|{0}|{1}|{2}", pattern, level, imgPath) :
string.Format("MFT_ALB|{0}|{1}|{2}", pattern, level, imgPath);
this.Frame.Navigate(typeof(GamePage), naviParam);
}
async private void OnCapturePhoto(object sender, RoutedEventArgs e)
{
if (ApplicationView.Value == ApplicationViewState.Snapped)
{
bool isUnsnapped = ApplicationView.TryUnsnap();
if (!isUnsnapped)
{
MessageDialog msgDlg = new MessageDialog("Please unsnap the application to select the image!", "FloTiles");
msgDlg.Commands.Add(new UICommand("Ok"));
msgDlg.CancelCommandIndex = 0;
await msgDlg.ShowAsync();
return;
}
}
StorageFile file = null;
var camera = new CameraCaptureUI();
file = await camera.CaptureFileAsync(CameraCaptureUIMode.Photo);
if (file == null)
return;
await Task.Run(async () =>
{
StorageFolder imageBackupFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("ImageBackup", CreationCollisionOption.OpenIfExists);
foreach (var fileBak in await imageBackupFolder.GetFilesAsync())
{
await fileBak.DeleteAsync();
}
file = await file.CopyAsync(imageBackupFolder, String.Format("FloTileImage{0}", file.FileType), NameCollisionOption.ReplaceExisting);
});
if (file == null)
return;
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read))
{
BitmapImage bitmap = new BitmapImage();
stream.Seek(0);
bitmap.SetSource(stream);
if (bitmap != null)
{
DefaultViewModel["SelectedImage"] = bitmap;
DefaultViewModel["SelectedImageName"] = file.Name;
CheckCanCreateFloTile();
}
}
}
}
FloTiles Logo
在结束本文之前,FloTiles Logo值得一提。一个出色的应用Logo就像蛋糕上的糖霜(和樱桃)!它能让应用更出色,并帮助人们记住它。
起初,我想创建一个可以玩转字母F和T的Logo。另一个设计是我构思了一个类似游戏中图像被分割成小块的各种大小拼图块的混合体。我想让Logo象征FloTiles游戏的灵魂。我花了两个星期来制作这个Logo。第一周,我花了几个小时进行草图绘制和尝试各种变体。到第一周结束时,我得出了最终的草图。我花了另一周时间将草图数字化,添加颜色,并最终将其打磨好。
最终的Logo展现了FloTiles游戏在运行时的流动性。如果你仔细看,可以看到四个不同颜色的拼图块。在FloTiles游戏中,当你选择一个拼图块并移动它时,它的尺寸会略微放大,透明度也会降低。Logo也描绘了这一点。它看起来像是蓝色的拼图块被选中并正在移动。如果你仔细看,在蓝色拼图块后面,有一个F和T字母的组合,象征着FloTiles的字母。
现在回想那两周,我感到满意,Logo做得很好,所有的努力都是值得的。
端点
这是我写过的最长的文章。我希望你喜欢它。请务必访问Windows应用商店并安装FloTiles(下载链接),并告诉我你的评论/建议。别忘了给应用评分。
历史
- 2012年12月1日:FloTiles文章已更新。
- 2012年11月29日:FloTiles v1.02在Windows应用商店发布。
- 2012年11月28日:FloTiles v1.01在Windows应用商店发布。
- 2012年11月21日:FloTiles v1.00在Windows应用商店发布。
- 2012年10月17日:FloTiles概念发布。