├── Licence.txt ├── README.md ├── SudokuMaster.sln ├── SudokuMaster ├── App.xaml ├── App.xaml.cs ├── ApplicationIcon.png ├── Bin │ └── Release │ │ └── SudokuMaster.xap ├── BoardModel.cs ├── BoardValue.cs ├── Cell.xaml ├── Cell.xaml.cs ├── GameOver.xaml ├── GameOver.xaml.cs ├── Gamelogic.cs ├── HighscoreItem.cs ├── HighscoresPage.xaml ├── HighscoresPage.xaml.cs ├── MainPage.xaml ├── MainPage.xaml.cs ├── MemoryDiagnosticsControl.cs ├── NumberSelection.xaml ├── NumberSelection.xaml.cs ├── Point.cs ├── Properties │ ├── AppManifest.xml │ ├── AssemblyInfo.cs │ └── WMAppManifest.xml ├── SoundHelper.cs ├── SplashScreenImage.jpg ├── SudokuMaster.csproj ├── WaitNote.xaml ├── WaitNote.xaml.cs ├── background.png ├── gfx │ ├── back.png │ ├── background.png │ ├── brownGridItem.png │ ├── darkGridItem.png │ ├── empty.png │ ├── highscores.png │ ├── lightGridItem.png │ ├── logo.png │ ├── move.png │ ├── newgame.png │ ├── statistic.png │ ├── time.png │ └── waitNote.png └── sounds │ ├── 60443__jobro__tada1.wav │ ├── 7040__yawfle__050816_chair_04.wav │ └── 7043__yawfle__050816_chair_07.wav └── doc ├── ClassDiagram.png ├── ScreenShot.png └── porting ├── Qt_Highscores.2.png ├── Qt_Highscores.png ├── Qt_Landscape.png ├── Qt_MainView.2.png ├── Qt_MainView.png ├── Qt_Numberpad.2.png ├── Qt_Numberpad.png ├── Qt_Options.png ├── Qt_Waitnote.2.png ├── Qt_Waitnote.png ├── SL_Highscores.2.png ├── SL_Landscape.png ├── SL_MainPage.png ├── SL_Numberpad.png ├── SL_Splashscreen.png └── SL_Waitnote.png /Licence.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2011-2014 Microsoft Mobile Oy. All rights reserved. 2 | 3 | Microsoft is a registered trademark of Microsoft Corporation. Nokia and HERE are 4 | trademarks and/or registered trademarks of Nokia Corporation. Other product and 5 | company names mentioned herein may be trademarks or trade names of their 6 | respective owners. 7 | 8 | 9 | License 10 | 11 | Subject to the conditions below, you may use, copy, modify and/or merge copies 12 | of this software and associated content and documentation files (the ”Software”) 13 | to test, develop, publish, distribute, sub-license and/or sell new software 14 | derived from or incorporating the Software, solely in connection with Microsoft 15 | Mobile devices (however branded). Some of the documentation, content and/or 16 | software may be licensed under open source software or other licenses. To the 17 | extent such documentation, content and/or software are included, those licenses 18 | and/or other terms and conditions shall apply in addition and/or instead of this 19 | notice. The exact terms of the licenses, disclaimers, acknowledgements and 20 | notices are reproduced in the materials provided, or in other obvious locations. 21 | No other license to any other intellectual property rights is granted herein. 22 | 23 | This file, unmodified, shall be included with all copies or substantial portions 24 | of the Software that are distributed in source code form. 25 | 26 | The Software cannot constitute the primary value of any new software derived 27 | from or incorporating the Software. 28 | 29 | Any person dealing with the Software shall not misrepresent the source of the 30 | Software. 31 | 32 | 33 | Disclaimer 34 | 35 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 36 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 37 | FOR A PARTICULAR PURPOSE, QUALITY AND NONINFRINGEMENT. IN NO EVENT SHALL THE 38 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES (INCLUDING, 39 | WITHOUT LIMITATION, DIRECT, SPECIAL, INDIRECT, PUNITIVE, CONSEQUENTIAL, 40 | EXEMPLARY AND/ OR INCIDENTAL DAMAGES) OR OTHER LIABILITY, WHETHER IN AN ACTION 41 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 42 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 43 | 44 | Microsoft Mobile Oy retains the right to make changes to this document at any 45 | time, without notice. 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Sudokumaster Silverlight Example 2 | ================================ 3 | 4 | Sudokumaster is a Sudoku mobile game developed with Silverlight for Windows 5 | Phone devices. The game is a logic-based, combinatorial number-placement 6 | puzzle with nine 3x3 grids each containing all the digits from 1 to 9. In the 7 | beginning only some of the numbers are placed in the grids and the player 8 | needs to figure out the correct positions for the missing numbers. 9 | 10 | The application is a rewrite of the Qt Sudokumaster application for Symbian 11 | and Maemo devices. 12 | 13 | 14 | PREREQUISITIES 15 | ------------------------------------------------------------------------------- 16 | 17 | - C# basics 18 | - Development environment 'Microsoft Visual Studio 2010 Express for Windows 19 | Phone' 20 | 21 | LINKS 22 | ------------------------------------------------------------------------------- 23 | 24 | Getting Started Guide: 25 | http://create.msdn.com/en-us/home/getting_started 26 | 27 | Learn About Windows Phone 7 Development: 28 | http://msdn.microsoft.com/fi-fi/ff380145 29 | 30 | App Hub, develop for Windows Phone: 31 | http://create.msdn.com 32 | 33 | 34 | IMPORTANT FILES 35 | ------------------------------------------------------------------------------- 36 | 37 | MainPage.xaml/.cs: Main page of the application, the game view. 38 | 39 | HighscoresPage.xaml/.cs: High scores (or top times) page, contains a list of 40 | 20 best times/moves. 41 | 42 | Gamelogic.cs: Game board generation, logic for the game. 43 | 44 | Cell.xaml/.cs: Represents a single cell on the game board. 45 | 46 | GameOver.xaml: Dialog which is shown when the game ends. 47 | 48 | NumberSelection.xaml/.cs: Dialog which is shown when the player taps on a cell. 49 | 50 | WaitNote.xaml/.cs: Spinning circle animation which is displayed while 51 | generating a new puzzle. 52 | 53 | 54 | KNOWN ISSUES 55 | ------------------------------------------------------------------------------- 56 | 57 | None. 58 | 59 | 60 | BUILD & INSTALLATION INSTRUCTIONS 61 | ------------------------------------------------------------------------------- 62 | 63 | **Preparations** 64 | 65 | 66 | Make sure you have the following installed: 67 | * Windows 7 68 | * Microsoft Visual Studio 2010 Express for Windows Phone 69 | * The Windows Phone Software Development Kit (SDK) 7.1 70 | http://go.microsoft.com/?linkid=9772716 71 | 72 | 73 | **Build on Microsoft Visual Studio** 74 | 75 | Please refer to: 76 | http://msdn.microsoft.com/en-us/library/ff928362.aspx 77 | 78 | 79 | **Deploy to Windows Phone 7** 80 | 81 | Please refer to: 82 | http://msdn.microsoft.com/en-us/library/gg588378.aspx 83 | 84 | 85 | 86 | RUNNING THE APPLICATION 87 | ------------------------------------------------------------------------------- 88 | 89 | An empty Sudoku game board is displayed after the application is started. The 90 | menu at the bottom of the screen contains two buttons, New Game and Highscores. 91 | Press the New Game button to start the game. Tap on an empty cell on the grid, 92 | and a dialog pops up where you can select a number or clear the value of the 93 | cell. Only the empty cells and the cells the player has set earlier (cells 94 | with white numbers) can be manipulated. The objective of the game is to fill 95 | the board with numbers between 1 and 9 according to the following guidelines: 96 | 97 | - A number can appear only once in each row 98 | - A number can appear only once in each column 99 | - A number can appear only once in each region 100 | 101 | A region is 3x3 squares, and the board is divided into 3x3 regions identified 102 | by lighter and darker cells. 103 | 104 | Below the board are three icons and numbers besides them; number of moves the 105 | player has made, remaining empty cells, and game time. 106 | 107 | The game ends when all the cells are filled. If a new high score was achieved, 108 | the player's name is queried. 109 | 110 | 111 | COMPATIBILITY 112 | ------------------------------------------------------------------------------- 113 | 114 | - Windows Phone 7 115 | 116 | Tested on: 117 | - Nokia Lumia 800 118 | - Nokia Lumia 900 119 | 120 | Developed with: 121 | - Microsoft Visual Studio 2010 Express for Windows Phone 122 | 123 | 124 | LICENCE 125 | ------------------------------------------------------------------------------- 126 | 127 | You can find license details in Licence.txt file provided with this project 128 | or online at 129 | https://github.com/Microsoft/sudokumaster-wp/blob/master/Licence.txt 130 | 131 | 132 | CHANGE HISTORY 133 | ------------------------------------------------------------------------------- 134 | 135 | 1.2 Code level improvements 136 | 1.1 Code quality improvements 137 | 1.0 First release 138 | -------------------------------------------------------------------------------- /SudokuMaster.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 Express for Windows Phone 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SudokuMaster", "SudokuMaster\SudokuMaster.csproj", "{8062FAC1-CF4C-4056-B3B7-5DFFE9EEF4B5}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {8062FAC1-CF4C-4056-B3B7-5DFFE9EEF4B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {8062FAC1-CF4C-4056-B3B7-5DFFE9EEF4B5}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {8062FAC1-CF4C-4056-B3B7-5DFFE9EEF4B5}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 15 | {8062FAC1-CF4C-4056-B3B7-5DFFE9EEF4B5}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {8062FAC1-CF4C-4056-B3B7-5DFFE9EEF4B5}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {8062FAC1-CF4C-4056-B3B7-5DFFE9EEF4B5}.Release|Any CPU.Deploy.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /SudokuMaster/App.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /SudokuMaster/App.xaml.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using System.Windows.Documents; 12 | using System.Windows.Input; 13 | using System.Windows.Media; 14 | using System.Windows.Media.Animation; 15 | using System.Windows.Navigation; 16 | using System.Windows.Shapes; 17 | using Microsoft.Phone.Controls; 18 | using Microsoft.Phone.Shell; 19 | using SudokuMaster.Debug; 20 | 21 | namespace SudokuMaster 22 | { 23 | public partial class App : Application 24 | { 25 | /// 26 | /// Provides easy access to the root frame of the Phone Application. 27 | /// 28 | /// The root frame of the Phone Application. 29 | public PhoneApplicationFrame RootFrame { get; private set; } 30 | 31 | /// 32 | /// Constructor for the Application object. 33 | /// 34 | public App() 35 | { 36 | // Global handler for uncaught exceptions. 37 | UnhandledException += Application_UnhandledException; 38 | 39 | // Show graphics profiling information while debugging. 40 | if (System.Diagnostics.Debugger.IsAttached) 41 | { 42 | // Display the current frame rate counters. 43 | Application.Current.Host.Settings.EnableFrameRateCounter = true; 44 | 45 | // Show the areas of the app that are being redrawn in each frame. 46 | // Application.Current.Host.Settings.EnableRedrawRegions = true; 47 | 48 | // Enable non-production analysis visualization mode, 49 | // which shows areas of a page that are being GPU accelerated with a colored overlay. 50 | // Application.Current.Host.Settings.EnableCacheVisualization = true; 51 | } 52 | 53 | // Standard Silverlight initialization 54 | InitializeComponent(); 55 | 56 | // Phone-specific initialization 57 | InitializePhoneApplication(); 58 | 59 | #if (DEBUG) 60 | // in debug mode display memory consuption 61 | MemoryDiagnosticsControl.Start(false); 62 | #endif 63 | } 64 | 65 | // Code to execute when the application is launching (eg, from Start) 66 | // This code will not execute when the application is reactivated 67 | private void Application_Launching(object sender, LaunchingEventArgs e) 68 | { 69 | Highscores.Load(); 70 | } 71 | 72 | // Code to execute when the application is activated (brought to foreground) 73 | // This code will not execute when the application is first launched 74 | private void Application_Activated(object sender, ActivatedEventArgs e) 75 | { 76 | Highscores.Load(); 77 | } 78 | 79 | // Code to execute when the application is deactivated (sent to background) 80 | // This code will not execute when the application is closing 81 | private void Application_Deactivated(object sender, DeactivatedEventArgs e) 82 | { 83 | } 84 | 85 | // Code to execute when the application is closing (eg, user hit Back) 86 | // This code will not execute when the application is deactivated 87 | private void Application_Closing(object sender, ClosingEventArgs e) 88 | { 89 | } 90 | 91 | // Code to execute if a navigation fails 92 | private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) 93 | { 94 | if (System.Diagnostics.Debugger.IsAttached) 95 | { 96 | // A navigation has failed; break into the debugger 97 | System.Diagnostics.Debugger.Break(); 98 | } 99 | } 100 | 101 | // Code to execute on Unhandled Exceptions 102 | private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e) 103 | { 104 | if (System.Diagnostics.Debugger.IsAttached) 105 | { 106 | // An unhandled exception has occurred; break into the debugger 107 | System.Diagnostics.Debugger.Break(); 108 | } 109 | } 110 | 111 | #region Phone application initialization 112 | 113 | // Avoid double-initialization 114 | private bool phoneApplicationInitialized = false; 115 | 116 | // Do not add any additional code to this method 117 | private void InitializePhoneApplication() 118 | { 119 | if (phoneApplicationInitialized) 120 | return; 121 | 122 | // Create the frame but don't set it as RootVisual yet; this allows the splash 123 | // screen to remain active until the application is ready to render. 124 | RootFrame = new PhoneApplicationFrame(); 125 | RootFrame.Navigated += CompleteInitializePhoneApplication; 126 | 127 | // Handle navigation failures 128 | RootFrame.NavigationFailed += RootFrame_NavigationFailed; 129 | 130 | // Ensure we don't initialize again 131 | phoneApplicationInitialized = true; 132 | } 133 | 134 | // Do not add any additional code to this method 135 | private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e) 136 | { 137 | // Set the root visual to allow the application to render 138 | if (RootVisual != RootFrame) 139 | RootVisual = RootFrame; 140 | 141 | // Remove this handler since it is no longer needed 142 | RootFrame.Navigated -= CompleteInitializePhoneApplication; 143 | } 144 | 145 | #endregion 146 | } 147 | } -------------------------------------------------------------------------------- /SudokuMaster/ApplicationIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/sudokumaster-wp/1207408de608b6d6a4f18b7c8bf2de649f6442ec/SudokuMaster/ApplicationIcon.png -------------------------------------------------------------------------------- /SudokuMaster/Bin/Release/SudokuMaster.xap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/sudokumaster-wp/1207408de608b6d6a4f18b7c8bf2de649f6442ec/SudokuMaster/Bin/Release/SudokuMaster.xap -------------------------------------------------------------------------------- /SudokuMaster/BoardModel.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | 6 | namespace SudokuMaster 7 | { 8 | public class BoardModel 9 | { 10 | public BoardValue[][] BoardNumbers { get; private set; } 11 | 12 | public BoardModel(int ColumnLength, int RowLength) 13 | { 14 | BoardNumbers = new BoardValue[RowLength][]; 15 | 16 | for (int x = 0; x < BoardNumbers.Length; x++) 17 | BoardNumbers[x] = new BoardValue[ColumnLength]; 18 | 19 | // create the actual objects 20 | for (int x = 0; x < BoardNumbers.Length; x++) 21 | { 22 | for (int y = 0; y < BoardNumbers[x].Length; y++) 23 | BoardNumbers[x][y] = new BoardValue(); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SudokuMaster/BoardValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.ComponentModel; 6 | 7 | namespace SudokuMaster 8 | { 9 | public class BoardValue : INotifyPropertyChanged 10 | { 11 | public event PropertyChangedEventHandler PropertyChanged; 12 | 13 | private int _value; 14 | 15 | /// 16 | /// Value representing number in particular cell 17 | /// 18 | public int Value 19 | { 20 | get 21 | { 22 | return _value; 23 | } 24 | set 25 | { 26 | _value = value; 27 | NotifyPropertyChanged("Value"); 28 | } 29 | } 30 | 31 | private bool _setByGame; 32 | 33 | /// 34 | /// Flag indicating if the number was set before game started, or by user 35 | /// 36 | public bool SetByGame 37 | { 38 | get 39 | { 40 | return _setByGame; 41 | } 42 | set 43 | { 44 | _setByGame = value; 45 | NotifyPropertyChanged("SetByGame"); 46 | } 47 | } 48 | 49 | private void NotifyPropertyChanged(string propertyName) 50 | { 51 | if (PropertyChanged != null) 52 | PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /SudokuMaster/Cell.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 47 | 48 | 49 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /SudokuMaster/Cell.xaml.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | using System.Windows; 6 | using System.Windows.Controls; 7 | using System.Windows.Input; 8 | using System.Windows.Media; 9 | 10 | namespace SudokuMaster 11 | { 12 | /// 13 | /// Represents a single cell on the board grid 14 | /// 15 | public partial class Cell : UserControl 16 | { 17 | // dependency properties so that we can use databinding in our custom control 18 | public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(Cell), new PropertyMetadata(OnValueChanged)); 19 | public static readonly DependencyProperty SetByGameProperty = DependencyProperty.Register("SetByGame", typeof(bool), typeof(Cell), new PropertyMetadata(true, OnSetByGameChanged)); 20 | 21 | // static brushes - optimization so that we don't create them every single time 22 | private static SolidColorBrush whiteBrush = new SolidColorBrush(Colors.White); 23 | private static SolidColorBrush blackBrush = new SolidColorBrush(Colors.Black); 24 | 25 | #region properties 26 | public bool SetByGame 27 | { 28 | get { return (bool)GetValue(SetByGameProperty); } 29 | set { SetValue(SetByGameProperty, value); } 30 | } 31 | 32 | public int Value 33 | { 34 | get { return (int)GetValue(ValueProperty); } 35 | set { SetValue(ValueProperty, value); } 36 | } 37 | 38 | public bool IsPlayerSettable 39 | { 40 | get { return !SetByGame; } 41 | } 42 | #endregion 43 | 44 | /// 45 | /// Constructor 46 | /// 47 | /// Parent, or owner of this cell 48 | public Cell() 49 | { 50 | InitializeComponent(); 51 | } 52 | 53 | /// 54 | /// Called when manipulation of the cell is completed. 55 | /// It's fired when user takes the finger off the screen, 56 | /// even if the fingers have drifted away from the control. 57 | /// 58 | /// Sender of the event 59 | /// Event arguments 60 | private void UserControl_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) 61 | { 62 | // Start the fade out animation which returns the cell's color back to original. 63 | fadeOutAnimation.Begin(); 64 | 65 | if (!IsPlayerSettable) 66 | SoundHelper.PlaySound(SoundHelper.SoundType.CellSelectedSound); 67 | else 68 | SoundHelper.PlaySound(SoundHelper.SoundType.NumberChosenSound); 69 | } 70 | 71 | /// 72 | /// Called when player touches the cell. 73 | /// Starts the fade in animation which gradually makes the cell red. 74 | /// 75 | /// Sender of the event. 76 | /// Event arguments. 77 | private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) 78 | { 79 | fadeInAnimation.Begin(); 80 | } 81 | 82 | /// 83 | /// Event fired when our SetByGame dependecy property change 84 | /// 85 | private static void OnSetByGameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 86 | { 87 | var cell = d as Cell; 88 | var value = (bool)e.NewValue; 89 | 90 | cell.textValue.Foreground = value ? blackBrush : whiteBrush; 91 | } 92 | 93 | /// 94 | /// Blink the cell 95 | /// 96 | public void Blink() 97 | { 98 | blinkAnimation.Begin(); 99 | } 100 | 101 | /// 102 | /// Event fired when our Value dependecy property change 103 | /// 104 | private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 105 | { 106 | var cell = d as Cell; 107 | var value = (int)e.NewValue; 108 | 109 | if (value == 0) 110 | cell.textValue.Text = ""; 111 | else 112 | cell.textValue.Text = value.ToString(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /SudokuMaster/GameOver.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 15 | 16 | 17 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 65 | 66 | 69 | 73 | 77 | 78 | 79 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 96 | 97 | 98 | 99 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /SudokuMaster/GameOver.xaml.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Net; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using System.Windows.Documents; 12 | using System.Windows.Input; 13 | using System.Windows.Media; 14 | using System.Windows.Media.Animation; 15 | using System.Windows.Shapes; 16 | 17 | namespace SudokuMaster 18 | { 19 | /// 20 | /// The dialog displayed when the puzzle is solved. 21 | /// Contains puzzle solving time and moves, and a texbox for player's name. 22 | /// 23 | public partial class GameOver : UserControl 24 | { 25 | HighscoreItem score; 26 | 27 | /// 28 | /// Constructor 29 | /// 30 | /// The score of the player. At least time and moves must be filled. 31 | public GameOver(HighscoreItem playerScore) 32 | { 33 | InitializeComponent(); 34 | 35 | // Start the fade in animation 36 | fadeInAnimation.Begin(); 37 | 38 | // Show the position textblock and player name textbox only if the 39 | // score is good enough to make it to the list. 40 | score = playerScore; 41 | int position = Highscores.IsNewHighscore(score); 42 | score.Index = position; 43 | if (position > 0) 44 | { 45 | playerName.Visibility = Visibility.Visible; 46 | textBlockPlacement.Text = "Your placement is " + position.ToString(); 47 | } 48 | else 49 | { 50 | playerName.Visibility = Visibility.Collapsed; 51 | ConfirmButton.Content = "Ok"; 52 | textBlockPlacement.Text = ""; 53 | } 54 | 55 | textBlockTime.Text = "Your time was " + score.Time.ToString(); 56 | } 57 | 58 | /// 59 | /// Called when the player presses a key on the keyboard 60 | /// 61 | /// Sender of the event. 62 | /// Event arguments. 63 | private void PlayerName_KeyDown(object sender, KeyEventArgs e) 64 | { 65 | // Add the score to the list and start fading out when the enter 66 | // key is pressed 67 | if (e.Key == Key.Enter) 68 | { 69 | Focus(); 70 | } 71 | } 72 | 73 | /// 74 | /// Called when the fade out animation is completed 75 | /// 76 | /// Sender of the event 77 | /// Event arguments 78 | private void FadeOutAnimation_Completed(object sender, EventArgs e) 79 | { 80 | // Remove this control from the parent UI element (game grid). Fade 81 | // out is just a visual effect, and this control would still exist 82 | // and receive events when it's not visible. 83 | (Parent as Panel).Children.Remove(this); 84 | } 85 | 86 | private void Button_Click(object sender, RoutedEventArgs e) 87 | { 88 | if (score.Index > 0) 89 | { 90 | score.Name = playerName.Text; 91 | Highscores.AddNewHighscore(score); 92 | playerName.IsReadOnly = true; 93 | } 94 | fadeOutAnimation.Begin(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SudokuMaster/Gamelogic.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace SudokuMaster 11 | { 12 | /// 13 | /// Contains the logic for generating and managing puzzles 14 | /// 15 | public class GameLogic 16 | { 17 | public BoardModel Model { get; private set; } 18 | 19 | public const int BlockSize = 3; 20 | public const int BlocksPerSide = 3; 21 | public const int RowLength = BlockSize * BlocksPerSide; 22 | public const int ColumnLength = RowLength; 23 | public const int MaxEmptyCells = 45; 24 | 25 | protected int[] randOrder; 26 | protected bool solutionFound = false; 27 | private int[][] copyCells; 28 | protected Random randGen = new Random(); 29 | 30 | public int EmptyCells { get; set; } 31 | public int PlayerMoves { get; set; } 32 | 33 | /// 34 | /// Constructor 35 | /// 36 | /// 9x9 array of Cells, the game board cells 37 | public GameLogic() 38 | { 39 | Model = new BoardModel(ColumnLength, RowLength); 40 | } 41 | 42 | /// 43 | /// Tests rows for conflicting cells 44 | /// 45 | /// X coordinate to check 46 | /// Y coordinate to check 47 | /// Number to test 48 | /// Tells whether we use the main array or a copy of it 49 | /// Conflicting point or null if no conflict 50 | private Point GetConflictsInRow(int x, int y, int value, bool useCopy) 51 | { 52 | for (int i = 0; i < RowLength; i++) 53 | { 54 | if (useCopy) 55 | { 56 | if (i != x && 57 | copyCells[i][y] != 0 && 58 | copyCells[i][y] == value) 59 | { 60 | return new Point(i, y); 61 | } 62 | } 63 | else 64 | { 65 | if (i != x && 66 | Model.BoardNumbers[i][y].Value != 0 && 67 | Model.BoardNumbers[i][y].Value == value) 68 | { 69 | return new Point(i, y);; 70 | } 71 | } 72 | } 73 | 74 | return null; 75 | } 76 | 77 | /// 78 | /// Tests columns for conflicting cells 79 | /// 80 | /// X coordinate to check 81 | /// Y coordinate to check 82 | /// Number to test 83 | /// Tells whether we use the main array or a copy of it 84 | /// Conflicting point or null if no conflict 85 | private Point GetConflictsInColumn(int x, int y, int value, bool useCopy) 86 | { 87 | for (int i = 0; i < ColumnLength; i++) 88 | { 89 | if (useCopy) 90 | { 91 | if (i != y && 92 | copyCells[x][i] != 0 && 93 | copyCells[x][i] == value) 94 | { 95 | return new Point(x, i); 96 | } 97 | } 98 | else 99 | { 100 | if (i != y && 101 | Model.BoardNumbers[x][i].Value != 0 && 102 | Model.BoardNumbers[x][i].Value == value) 103 | { 104 | return new Point(x, i); 105 | } 106 | } 107 | } 108 | return null; 109 | } 110 | 111 | /// 112 | /// Tests blocks (3x3) for conflicting cells 113 | /// 114 | /// X coordinate to check 115 | /// Y coordinate to check 116 | /// Number to test 117 | /// Tells whether we use the main array or a copy of it 118 | /// Conflicting point or null if no conflict 119 | private Point TestBlock(int x, int y, int value, bool useCopy) 120 | { 121 | int blocksFirstX = ((int)(x / 3)) * 3; 122 | int blocksFirstY = ((int)(y / 3)) * 3; 123 | for (int i = blocksFirstX; i < blocksFirstX + 3; i++) 124 | { 125 | for (int j = blocksFirstY; j < blocksFirstY + 3; j++) 126 | { 127 | if (useCopy) 128 | { 129 | if (copyCells[i][j] != 0) 130 | if (i != x && j != y && copyCells[i][j] == value) 131 | { 132 | return new Point(i, j); 133 | } 134 | } 135 | else 136 | { 137 | if (Model.BoardNumbers[i][j].Value != 0) 138 | if (i != x && j != y && Model.BoardNumbers[i][j].Value == value) 139 | { 140 | return new Point(i, j); 141 | } 142 | } 143 | } 144 | } 145 | return null; 146 | } 147 | 148 | /// 149 | /// Tries to set the given number to the given index. 150 | /// 151 | /// X coordinate on the grid 152 | /// Y coordinate on the grid 153 | /// Number to set 154 | /// Tells whether we use the main array or a copy of it 155 | /// true if it was possible, false otherwise. 156 | private bool SetNumber(int x, int y, int value, bool useCopy) 157 | { 158 | if (GetConflictsInRow(x, y, value, useCopy) == null && 159 | GetConflictsInColumn(x, y, value, useCopy) == null && 160 | TestBlock(x, y, value, useCopy) == null) 161 | { 162 | if (useCopy) 163 | copyCells[x][y] = value; 164 | else 165 | Model.BoardNumbers[x][y].Value = value; 166 | 167 | return true; 168 | } 169 | return false; 170 | } 171 | 172 | /// 173 | /// Copies cell values from main array to the copy array 174 | /// 175 | private void MakeCopy() 176 | { 177 | copyCells = new int[RowLength][]; 178 | 179 | for (int i = 0; i < RowLength; i++) 180 | { 181 | copyCells[i] = new int[ColumnLength]; 182 | 183 | for (int j = 0; j < ColumnLength; j++) 184 | copyCells[i][j] = Model.BoardNumbers[i][j].Value; 185 | } 186 | } 187 | 188 | /// 189 | /// Checks whether the puzzle has an unique solution. 190 | /// The algorithm is mainly the same as is fillCell(). 191 | /// 192 | /// X coordinate on the grid 193 | /// Y coordinate on the grid 194 | /// true if if it does, false otherwise. 195 | public bool CheckUniqueness(int x, int y) 196 | { 197 | if (y == RowLength) 198 | { 199 | if (solutionFound) 200 | { 201 | solutionFound = false; 202 | return true; 203 | } 204 | solutionFound = true; 205 | return false; 206 | } 207 | 208 | int nextX = x + 1, nextY = y; 209 | if (x == RowLength - 1) 210 | { 211 | nextX = 0; 212 | nextY = y + 1; 213 | } 214 | 215 | if (Model.BoardNumbers[x][y].Value != 0) 216 | { 217 | if (CheckUniqueness(nextX, nextY)) 218 | return true; 219 | } 220 | else 221 | { 222 | for (int i = 1; i <= 9; i++) 223 | if (SetNumber(x, y, i, true)) 224 | { 225 | if (CheckUniqueness(nextX, nextY)) 226 | return true; 227 | 228 | Model.BoardNumbers[x][y].Value = 0; 229 | } 230 | } 231 | return false; 232 | } 233 | 234 | /// 235 | /// Removes numbers from the board until a desired puzzle is obtained. 236 | /// Checks after every deletion that the puzzle still has an unique solution. 237 | /// 238 | private void RemoveCells() 239 | { 240 | while (EmptyCells < MaxEmptyCells) 241 | { 242 | int randX = randGen.Next(ColumnLength); 243 | int randY = randGen.Next(RowLength); 244 | int temp = Model.BoardNumbers[randX][randY].Value; 245 | 246 | if (temp != 0) 247 | { 248 | Model.BoardNumbers[randX][randY].Value = 0; 249 | MakeCopy(); 250 | 251 | if (CheckUniqueness(0, 0)) 252 | Model.BoardNumbers[randX][randY].Value = temp; 253 | else 254 | EmptyCells++; 255 | } 256 | } 257 | } 258 | 259 | /// 260 | /// Generates a random full sudoku board by using recursive 261 | /// backtracking method. 262 | /// 263 | /// X coordinate on the grid 264 | /// Y coordinate on the grid 265 | private bool FillCell(int x, int y) 266 | { 267 | if (y == RowLength) 268 | return true; 269 | 270 | int nextX = x + 1, nextY = y; 271 | if (x == ColumnLength - 1) 272 | { 273 | nextX = 0; 274 | nextY = y + 1; 275 | } 276 | 277 | for (int i = 0; i < RowLength; i++) 278 | { 279 | if (SetNumber(x, y, randOrder[i], false) && FillCell(nextX, nextY)) 280 | return true; 281 | 282 | Model.BoardNumbers[x][y].Value = 0; 283 | } 284 | return false; 285 | } 286 | 287 | /// 288 | /// Creates an array with numbers 1-9 in it, ordered randomly. 289 | /// 290 | private void FillRandOrder() 291 | { 292 | randOrder = new int[RowLength]; 293 | bool isSet = false; 294 | int rand, j; 295 | for (int i = 0; i < RowLength; i++) 296 | { 297 | while (!isSet) 298 | { 299 | rand = randGen.Next(ColumnLength) + 1; 300 | for (j = 0; j < ColumnLength; j++) 301 | if (rand == randOrder[j]) 302 | break; 303 | if (j == ColumnLength) 304 | { 305 | randOrder[i] = rand; 306 | isSet = true; 307 | } 308 | } 309 | isSet = false; 310 | } 311 | } 312 | 313 | /// 314 | /// Generates a new puzzle 315 | /// 316 | public void GeneratePuzzle() 317 | { 318 | for (int i = 0; i < RowLength; i++) 319 | for (int j = 0; j < ColumnLength; j++) 320 | Model.BoardNumbers[i][j].Value = 0; 321 | 322 | FillRandOrder(); 323 | FillCell(0, 0); 324 | EmptyCells = 0; 325 | PlayerMoves = 0; 326 | RemoveCells(); 327 | 328 | for (int i = 0; i < RowLength; i++) 329 | { 330 | for (int j = 0; j < ColumnLength; j++) 331 | Model.BoardNumbers[i][j].SetByGame = Model.BoardNumbers[i][j].Value != 0; 332 | } 333 | } 334 | 335 | /// 336 | /// Places the number selected by the player on the game board 337 | /// 338 | /// X coordinate to set 339 | /// Y coordinate to set 340 | /// Number to set 341 | public List SetNumberByPlayer(int x, int y, int value) 342 | { 343 | var collisions = new List(); 344 | 345 | var rowConflicts = GetConflictsInRow(x, y, value, false); 346 | var columnConflicts = GetConflictsInColumn(x, y, value, false); 347 | var blockConflicts = TestBlock(x, y, value, false); 348 | 349 | if (rowConflicts == null && 350 | columnConflicts == null && 351 | blockConflicts == null) 352 | { 353 | if (Model.BoardNumbers[x][y].Value != 0 && value == 0) 354 | EmptyCells++; 355 | else if (Model.BoardNumbers[x][y].Value == 0 && value != 0) 356 | EmptyCells--; 357 | 358 | Model.BoardNumbers[x][y].Value = value; 359 | } 360 | 361 | PlayerMoves++; 362 | 363 | if (rowConflicts != null) 364 | collisions.Add(rowConflicts); 365 | 366 | if (columnConflicts != null) 367 | collisions.Add(columnConflicts); 368 | 369 | if (blockConflicts != null) 370 | collisions.Add(blockConflicts); 371 | 372 | return collisions; 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /SudokuMaster/HighscoreItem.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | using System; 6 | using System.Xml; 7 | using System.Xml.Serialization; 8 | 9 | namespace SudokuMaster 10 | { 11 | /// 12 | /// Represents a single score in highscore list. 13 | /// 14 | public class HighscoreItem 15 | { 16 | public int Index { get; set; } 17 | public string Name { get; set; } 18 | public int Moves { get; set; } 19 | 20 | // XmlSerializer cannot serialize TimeSpans. Tell the serializer to 21 | // ignore Time, and (de)serialize the Time as string instead. 22 | [XmlIgnore] 23 | public TimeSpan Time { get; set; } 24 | 25 | [XmlAttribute("TimeString", DataType = "duration")] 26 | public string XmlTime 27 | { 28 | get { return XmlConvert.ToString(Time); } 29 | set { Time = XmlConvert.ToTimeSpan(value); } 30 | } 31 | 32 | /// 33 | /// Constructor 34 | /// Serializable classes must have default constructor without any 35 | /// arguments. XmlSerializer knows the member variables of given class, 36 | /// but nothing about the constructor's arguments. 37 | /// 38 | public HighscoreItem() 39 | { 40 | Index = 0; 41 | } 42 | 43 | /// 44 | /// Constructor 45 | /// 46 | /// Position in highscore list 47 | /// Player's name 48 | /// Time needed to solve the puzzle 49 | /// Moves needed to solve the puzzle 50 | public HighscoreItem(int scoreIndex, string playerName, TimeSpan solvingTime, int playerMoves) 51 | { 52 | Index = scoreIndex; 53 | Name = playerName; 54 | Time = solvingTime; 55 | Moves = playerMoves; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /SudokuMaster/HighscoresPage.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 35 | 36 | 38 | 39 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /SudokuMaster/HighscoresPage.xaml.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.IO.IsolatedStorage; 9 | using System.Xml.Serialization; 10 | using Microsoft.Phone.Controls; 11 | 12 | namespace SudokuMaster 13 | { 14 | /// 15 | /// The highscores page. 16 | /// The page contains just the title and a listbox. Loading and saving 17 | /// the scores is done with XmlSerializer. 18 | /// 19 | public partial class Highscores : PhoneApplicationPage 20 | { 21 | const string highscoresFilename = "highscores.xml"; 22 | public static List scores; 23 | 24 | /// 25 | /// Constructor 26 | /// Initializes the component and populates the listbox. 27 | /// 28 | public Highscores() 29 | { 30 | InitializeComponent(); 31 | HighscoreList.ItemsSource = scores; 32 | } 33 | 34 | /// 35 | /// Loads the highscores from isolated storage 36 | /// 37 | static public void Load() 38 | { 39 | scores = new List(); 40 | IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); 41 | 42 | // Create empty list if the highscores file does not exist. 43 | // This is needed when the application is started for the first time. 44 | if (!store.FileExists(highscoresFilename)) 45 | { 46 | for (int i = 1; i <= 20; i++) 47 | { 48 | scores.Add(new HighscoreItem(i, "Sudokumaster", 49 | new TimeSpan(0, 59, 59), 100)); 50 | } 51 | Save(); 52 | return; 53 | } 54 | 55 | // Open the file and use XmlSerializer to deserialize the xml file into 56 | // a list of HighscoreItems. 57 | using (IsolatedStorageFileStream stream = store.OpenFile(highscoresFilename, FileMode.Open)) 58 | { 59 | using (StreamReader reader = new StreamReader(stream)) 60 | { 61 | XmlSerializer serializer = new XmlSerializer(scores.GetType()); 62 | scores = (List)serializer.Deserialize(reader); 63 | } 64 | } 65 | } 66 | 67 | /// 68 | /// Saves the highscores to isolated storage. 69 | /// 70 | static public void Save() 71 | { 72 | IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); 73 | 74 | // Open the file and use XmlSerializer to serialize the list into the file 75 | using (IsolatedStorageFileStream stream = store.CreateFile(highscoresFilename)) 76 | { 77 | using (StreamWriter writer = new StreamWriter(stream)) 78 | { 79 | XmlSerializer serializer = new XmlSerializer(scores.GetType()); 80 | serializer.Serialize(writer, scores); 81 | writer.Flush(); 82 | } 83 | } 84 | } 85 | 86 | /// 87 | /// Checks if given score is a new highscore 88 | /// 89 | /// Score to check. The score should contain at least the solving time and moves. 90 | /// The position in highscore list, or zero if the score doesn't make it to the list 91 | static public int IsNewHighscore(HighscoreItem score) 92 | { 93 | foreach (HighscoreItem item in scores) 94 | { 95 | // Check the time, and if the times are the same, check the 96 | // moves needed to solve the puzzle 97 | if (score.Time < item.Time || 98 | (score.Time == item.Time && score.Moves < item.Moves)) 99 | return item.Index; 100 | } 101 | 102 | return 0; 103 | } 104 | 105 | /// 106 | /// Add a new score to highscore list 107 | /// 108 | /// Score to add. All members of the score should be filled. 109 | static public void AddNewHighscore(HighscoreItem score) 110 | { 111 | // Insert the score into the list, remove weakest score from the list 112 | // and save the list. 113 | if (score.Index <= 0) 114 | return; 115 | scores.Insert(score.Index - 1, score); 116 | scores.RemoveAt(scores.Count - 1); 117 | for (int t = score.Index; t < scores.Count; t++) 118 | scores[t].Index++; 119 | Save(); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /SudokuMaster/MainPage.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /SudokuMaster/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | using System; 6 | using System.IO; 7 | using System.IO.IsolatedStorage; 8 | using System.Linq; 9 | using System.Threading; 10 | using System.Windows; 11 | using System.Windows.Controls; 12 | using System.Windows.Data; 13 | using System.Windows.Input; 14 | using System.Windows.Media.Imaging; 15 | using System.Windows.Threading; 16 | using Microsoft.Phone.Controls; 17 | using Microsoft.Phone.Shell; 18 | 19 | namespace SudokuMaster 20 | { 21 | /// 22 | /// Main page of the application, the game itself 23 | /// 24 | public partial class MainPage : PhoneApplicationPage 25 | { 26 | /// 27 | /// Possible game states; game not started yet, ongoing and game over 28 | /// 29 | enum GameState 30 | { 31 | NotStarted = 0, 32 | Ongoing, 33 | GameOver 34 | }; 35 | 36 | const String gameStateFile = "gamestate.dat"; 37 | private GameLogic game; 38 | private GameState gameState = GameState.NotStarted; 39 | private DispatcherTimer gameTimer; 40 | private DateTime gameStartTime; 41 | private DateTime gamePausedTime; 42 | private TimeSpan gameTimeElapsed; 43 | private Cell[][] cells; 44 | 45 | /// 46 | /// Constructor 47 | /// 48 | public MainPage() 49 | { 50 | InitializeComponent(); 51 | 52 | // Initialize the grid and instantiate the logic 53 | game = new GameLogic(); 54 | gameTimer = new DispatcherTimer(); 55 | gameTimer.Interval = TimeSpan.FromSeconds(1); 56 | gameTimer.Tick += StatusTimerTick; 57 | 58 | gamePausedTime = new DateTime(); 59 | 60 | cells = CreateGrid(); 61 | 62 | // For tombstoning; listen for deactivated event and restore state 63 | // if the application was deactivated earlier. 64 | PhoneApplicationService.Current.Deactivated += new EventHandler(App_Deactivated); 65 | RestoreState(); 66 | } 67 | 68 | /// 69 | /// Called when a page becomes the active page in a frame. 70 | /// 71 | /// Event arguments 72 | protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) 73 | { 74 | if (gameState == GameState.Ongoing && gamePausedTime > gameStartTime) 75 | { 76 | gameStartTime += DateTime.Now - gamePausedTime; 77 | UpdateStatus(); 78 | } 79 | } 80 | 81 | /// 82 | /// Event handler for the highscores -button. 83 | /// Navigates to the highscores page. 84 | /// 85 | /// Sender of the event 86 | /// Event arguments 87 | private void HighscoresButton_Click(object sender, EventArgs e) 88 | { 89 | gamePausedTime = DateTime.Now; 90 | 91 | NavigationService.Navigate(new Uri("/HighscoresPage.xaml", 92 | UriKind.RelativeOrAbsolute)); 93 | } 94 | 95 | /// 96 | /// Event handler for the new game -button 97 | /// 98 | /// Sender of the event. 99 | /// Event arguments. 100 | private void NewGameButton_Click(object sender, EventArgs e) 101 | { 102 | if (gameState == GameState.Ongoing) 103 | { 104 | MessageBoxResult result = MessageBox.Show("Do you really want to start new game? \nAny game progress will be lost.", "", MessageBoxButton.OKCancel); 105 | 106 | if (result == MessageBoxResult.OK) 107 | { 108 | NewGame(); 109 | } 110 | } 111 | else 112 | NewGame(); 113 | } 114 | 115 | 116 | /// 117 | /// Event handler for the status timer 118 | /// 119 | /// Sender of the event. 120 | /// Event arguments. 121 | private void StatusTimerTick(object sender, EventArgs e) 122 | { 123 | UpdateStatus(); 124 | } 125 | 126 | /// 127 | /// Generates a new puzzle and starts the game 128 | /// 129 | private void NewGame() 130 | { 131 | // Close the GameOver dialog if it was still active 132 | GameOver gameOver = LayoutRoot.Children.OfType().SingleOrDefault(); 133 | LayoutRoot.Children.Remove(gameOver); 134 | 135 | numberSelection.Visibility = System.Windows.Visibility.Collapsed; 136 | 137 | // Display wait note (spinning circle) 138 | waitIndicator.Visibility = System.Windows.Visibility.Visible; 139 | waitIndicator.StartSpin(); 140 | 141 | // Disable databinding while generating puzzle 142 | DataContext = null; 143 | 144 | // Puzzle generation takes couple of seconds, do it in another thread 145 | ThreadPool.QueueUserWorkItem(dummy => 146 | { 147 | // generating puzzle doesn't touch UI so it can run on another thread 148 | game.GeneratePuzzle(); 149 | 150 | // switching to UI thread to modify UI components 151 | Deployment.Current.Dispatcher.BeginInvoke(() => 152 | { 153 | DataContext = game.Model; // let's turn on databinding again 154 | gameTimer.Start(); 155 | gameStartTime = DateTime.Now; 156 | gameState = GameState.Ongoing; 157 | UpdateStatus(); 158 | waitIndicator.Visibility = System.Windows.Visibility.Collapsed; 159 | waitIndicator.StopSpin(); 160 | }); 161 | }); 162 | } 163 | 164 | /// 165 | /// Updates status to UI; player moves, empty cells and game time 166 | /// 167 | private void UpdateStatus() 168 | { 169 | gameTimeElapsed = DateTime.Now - gameStartTime; 170 | 171 | GameTime.Text = String.Format("{0:D1}:{1:D2}:{2:D2}", 172 | gameTimeElapsed.Hours, gameTimeElapsed.Minutes, gameTimeElapsed.Seconds); 173 | 174 | Empty.Text = game.EmptyCells.ToString(); 175 | Moves.Text = game.PlayerMoves.ToString(); 176 | 177 | } 178 | 179 | /// 180 | /// Ends current game. Called when all the cells are filled. 181 | /// 182 | private void GameEnds() 183 | { 184 | gameTimer.Stop(); 185 | 186 | // Blink all cells and prevent the player from modifying the cells 187 | for (int row = 0; row < GameLogic.RowLength; row++) 188 | { 189 | for (int col = 0; col < GameLogic.ColumnLength; col++) 190 | { 191 | game.Model.BoardNumbers[row][col].SetByGame = true; // to block the user input 192 | cells[row][col].Blink(); 193 | } 194 | } 195 | 196 | // Display the score with GameOver dialog 197 | HighscoreItem score = new HighscoreItem(); 198 | score.Time = new TimeSpan(gameTimeElapsed.Days, gameTimeElapsed.Hours, 199 | gameTimeElapsed.Minutes, gameTimeElapsed.Seconds, 0); 200 | score.Moves = game.PlayerMoves; 201 | 202 | 203 | //TODO: move this to XAML 204 | GameOver gameOver = new GameOver(score); 205 | // Main page is divided into 2x3 grid. Make sure the row and column 206 | // properties are set properly (position 0,0 with span 2,3) to make 207 | // the dialog visible anywhere on the page. 208 | gameOver.SetValue(Grid.RowSpanProperty, 3); 209 | gameOver.SetValue(Grid.ColumnSpanProperty, 2); 210 | gameOver.SetValue(Grid.VerticalAlignmentProperty, VerticalAlignment.Center); 211 | gameOver.SetValue(Grid.HorizontalAlignmentProperty, HorizontalAlignment.Center); 212 | gameOver.SetValue(MarginProperty, new Thickness(10, 0, 0, 0)); 213 | 214 | LayoutRoot.Children.Add(gameOver); 215 | gameState = GameState.GameOver; 216 | 217 | SoundHelper.PlaySound(SoundHelper.SoundType.GameEndSound); 218 | } 219 | 220 | /// 221 | /// Creates the grid cells and populates the board grid with the cells 222 | /// 223 | /// 9x9 array of empty cells 224 | private Cell[][] CreateGrid() 225 | { 226 | var darkImage = new BitmapImage(new Uri("/gfx/darkGridItem.png", UriKind.Relative)); 227 | var lightImage = new BitmapImage(new Uri("/gfx/lightGridItem.png", UriKind.Relative)); 228 | 229 | bool lightCell = false; 230 | Cell[][] cells = new Cell[GameLogic.RowLength][]; 231 | 232 | for (int row = 0; row < GameLogic.RowLength; row++) 233 | { 234 | cells[row] = new Cell[GameLogic.ColumnLength]; 235 | 236 | if (row % GameLogic.BlockSize != 0) 237 | lightCell = !lightCell; 238 | 239 | for (int col = 0; col < GameLogic.ColumnLength; col++) 240 | { 241 | // switch image type (light or dark) after each 3 cells in row 242 | if (col % GameLogic.BlockSize == 0) 243 | lightCell = !lightCell; 244 | 245 | Cell c = new Cell(); 246 | c.SetValue(Grid.RowProperty, row); 247 | c.SetValue(Grid.ColumnProperty, col); 248 | c.BackgroundImage.Source = lightCell ? lightImage : darkImage; 249 | 250 | // install event handler 251 | c.MouseLeftButtonDown += new MouseButtonEventHandler(OnCellTouched); 252 | 253 | // set binding to proper BoardValue from BoardViewModel 254 | Binding b = new Binding(string.Format("BoardNumbers[{0}][{1}].Value", row, col)); 255 | c.SetBinding(Cell.ValueProperty, b); 256 | 257 | Binding b2 = new Binding(string.Format("BoardNumbers[{0}][{1}].SetByGame", row, col)); 258 | c.SetBinding(Cell.SetByGameProperty, b2); 259 | 260 | cells[row][col] = c; 261 | BoardGrid.Children.Add(c); 262 | } 263 | } 264 | 265 | return cells; 266 | } 267 | 268 | private void OnCellTouched(object sender, MouseButtonEventArgs e) 269 | { 270 | Cell cell = sender as Cell; 271 | 272 | if (!cell.IsPlayerSettable) 273 | return; 274 | 275 | // This lambda experssion will allow us to have access to destination cell in a clean way 276 | numberSelection.OnSelectedNumber = (selectedNumber => OnNumberChoosen(cell, selectedNumber)); 277 | 278 | // Place the dialog above the cell, but make sure the dialog fits on the screen. 279 | numberSelection.KeyboardMargin = GetPositionForCell(cell); 280 | 281 | // Change the visibility + start fade in animation 282 | numberSelection.ShowKeyboard(); 283 | } 284 | 285 | /// 286 | /// Helper method to get the absolute position with respect to the screen borders 287 | /// 288 | private Thickness GetPositionForCell(Cell cell) 289 | { 290 | var pos = new System.Windows.Point(cell.ActualWidth / 2 - numberSelection.KeyboardSize.Width / 2, cell.ActualHeight / 2 - numberSelection.KeyboardSize.Height / 2); 291 | pos = cell.TransformToVisual(LayoutRoot).Transform(pos); 292 | 293 | if (pos.X < 0) 294 | pos.X = 0; 295 | else if (pos.X > LayoutRoot.ActualWidth - numberSelection.KeyboardSize.Width) 296 | pos.X = LayoutRoot.ActualWidth - numberSelection.KeyboardSize.Width; 297 | 298 | if (pos.Y < 0) 299 | pos.Y = 0; 300 | else if (pos.Y > LayoutRoot.ActualHeight - numberSelection.KeyboardSize.Height) 301 | pos.Y = LayoutRoot.ActualHeight - numberSelection.KeyboardSize.Height; 302 | 303 | return new Thickness(pos.X, pos.Y, 0, 0); 304 | } 305 | 306 | /// 307 | /// Action triggered when user selected a number 308 | /// 309 | private void OnNumberChoosen(Cell sender, int number) 310 | { 311 | var conflictingCells = game.SetNumberByPlayer((int)sender.GetValue(Grid.RowProperty), 312 | (int)sender.GetValue(Grid.ColumnProperty), 313 | number); 314 | 315 | foreach (var point in conflictingCells) 316 | cells[point.X][point.Y].Blink(); 317 | 318 | SoundHelper.PlaySound(SoundHelper.SoundType.CellSelectedSound); 319 | 320 | if (gameState != GameState.NotStarted && game.EmptyCells == 0) 321 | GameEnds(); 322 | } 323 | 324 | /// 325 | /// Event handler for application deactivation. 326 | /// Stores the current game state into a file. 327 | /// 328 | /// Sender of the event. 329 | /// Event arguments. 330 | void App_Deactivated(object sender, DeactivatedEventArgs e) 331 | { 332 | StoreState(); 333 | } 334 | 335 | /// 336 | /// Reads the game state from a file and continues the game from where 337 | /// it was left. 338 | /// 339 | private void RestoreState() 340 | { 341 | IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); 342 | if (!store.FileExists(gameStateFile)) 343 | return; 344 | 345 | int emptyCells = 0; 346 | using (IsolatedStorageFileStream stream = store.OpenFile(gameStateFile, FileMode.Open)) 347 | { 348 | using (BinaryReader reader = new BinaryReader(stream)) 349 | { 350 | // Read the state and stats 351 | gameState = (GameState)reader.ReadInt32(); 352 | game.PlayerMoves = reader.ReadInt32(); 353 | gameTimeElapsed = new TimeSpan(reader.ReadInt64()); 354 | gameStartTime = DateTime.Now - gameTimeElapsed; 355 | 356 | // Read contents of the cells 357 | for (int row = 0; row < GameLogic.RowLength; row++) 358 | { 359 | for (int col = 0; col < GameLogic.ColumnLength; col++) 360 | { 361 | int value = reader.ReadInt32(); 362 | game.Model.BoardNumbers[row][col].Value = value; 363 | game.Model.BoardNumbers[row][col].SetByGame = reader.ReadBoolean(); 364 | 365 | if (value == 0) 366 | emptyCells++; 367 | } 368 | } 369 | } 370 | } 371 | 372 | store.DeleteFile(gameStateFile); 373 | 374 | if (gameState == GameState.Ongoing) 375 | { 376 | game.EmptyCells = emptyCells; 377 | gameTimer.Start(); 378 | } 379 | else 380 | { 381 | game.EmptyCells = 0; 382 | } 383 | 384 | DataContext = game.Model; 385 | UpdateStatus(); 386 | } 387 | 388 | /// 389 | /// Stores current game state to a file 390 | /// 391 | private void StoreState() 392 | { 393 | IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); 394 | 395 | using (IsolatedStorageFileStream stream = store.CreateFile(gameStateFile)) 396 | { 397 | using (BinaryWriter writer = new BinaryWriter(stream)) 398 | { 399 | 400 | writer.Write((Int32)gameState); 401 | writer.Write(game.PlayerMoves); 402 | writer.Write((Int64)gameTimeElapsed.Ticks); 403 | 404 | // Contents of the cells 405 | for (int row = 0; row < GameLogic.RowLength; row++) 406 | { 407 | for (int col = 0; col < GameLogic.ColumnLength; col++) 408 | { 409 | writer.Write(game.Model.BoardNumbers[row][col].Value); 410 | writer.Write(game.Model.BoardNumbers[row][col].SetByGame); 411 | } 412 | } 413 | } 414 | } 415 | } 416 | 417 | /// 418 | /// Event handler for orientation changes. 419 | /// Repositions UI elements depending on the orientation. 420 | /// 421 | /// Sender of the event 422 | /// Event arguments 423 | private void PhoneApplicationPage_OrientationChanged(object sender,OrientationChangedEventArgs e) 424 | { 425 | if (e.Orientation == PageOrientation.Landscape || 426 | e.Orientation == PageOrientation.LandscapeLeft || 427 | e.Orientation == PageOrientation.LandscapeRight) 428 | { 429 | Logo.SetValue(Grid.RowProperty, 1); 430 | Logo.SetValue(Grid.ColumnSpanProperty, 1); 431 | 432 | BoardGrid.SetValue(Grid.RowProperty, 0); 433 | BoardGrid.SetValue(Grid.ColumnProperty, 1); 434 | BoardGrid.SetValue(Grid.RowSpanProperty, 3); 435 | BoardGrid.SetValue(Grid.ColumnSpanProperty, 2); 436 | 437 | waitIndicator.SetValue(Grid.RowProperty, 0); 438 | waitIndicator.SetValue(Grid.ColumnProperty, 1); 439 | waitIndicator.SetValue(Grid.RowSpanProperty, 3); 440 | waitIndicator.SetValue(Grid.ColumnSpanProperty, 2); 441 | 442 | Statistics.SetValue(Grid.RowProperty, 1); 443 | Statistics.SetValue(Grid.RowSpanProperty, 2); 444 | Statistics.SetValue(Grid.ColumnSpanProperty, 1); 445 | 446 | if(e.Orientation == PageOrientation.LandscapeLeft) 447 | LayoutRoot.Margin = new Thickness(0 ,0 ,72 ,0); 448 | if (e.Orientation == PageOrientation.LandscapeRight) 449 | LayoutRoot.Margin = new Thickness(72, 0, 0, 0); 450 | 451 | LayoutRoot.RowDefinitions[0].Height = new GridLength(90); 452 | LayoutRoot.RowDefinitions[1].Height = new GridLength(90); 453 | 454 | for (int t = 0; t < Statistics.ColumnDefinitions.Count; t++) 455 | Statistics.ColumnDefinitions[t].Width = new GridLength(0); 456 | 457 | Statistics.ColumnDefinitions[0].Width = new GridLength(10); 458 | Statistics.ColumnDefinitions[1].Width = new GridLength(35, GridUnitType.Star); 459 | Statistics.ColumnDefinitions[2].Width = new GridLength(65, GridUnitType.Star); 460 | 461 | Statistics.RowDefinitions[0].Height = new GridLength(10); 462 | Statistics.RowDefinitions[1].Height = new GridLength(100, GridUnitType.Star); 463 | Statistics.RowDefinitions[2].Height = new GridLength(100, GridUnitType.Star); 464 | Statistics.RowDefinitions[3].Height = new GridLength(100, GridUnitType.Star); 465 | Statistics.RowDefinitions[4].Height = new GridLength(10); 466 | 467 | Statistics.Height = 192; 468 | 469 | MovesImage.SetValue(Grid.ColumnProperty, 1); 470 | MovesImage.SetValue(Grid.RowProperty, 1); 471 | 472 | EmptyImage.SetValue(Grid.ColumnProperty, 1); 473 | EmptyImage.SetValue(Grid.RowProperty, 2); 474 | 475 | GameTimeImage.SetValue(Grid.ColumnProperty, 1); 476 | GameTimeImage.SetValue(Grid.RowProperty, 3); 477 | 478 | Moves.SetValue(Grid.ColumnProperty, 2); 479 | Moves.SetValue(Grid.RowProperty, 1); 480 | 481 | Empty.SetValue(Grid.ColumnProperty, 2); 482 | Empty.SetValue(Grid.RowProperty, 2); 483 | 484 | GameTime.SetValue(Grid.ColumnProperty, 2); 485 | GameTime.SetValue(Grid.RowProperty, 3); 486 | } 487 | else 488 | { 489 | Logo.SetValue(Grid.RowProperty, 0); 490 | Logo.SetValue(Grid.ColumnSpanProperty, 2); 491 | 492 | BoardGrid.SetValue(Grid.RowProperty, 1); 493 | BoardGrid.SetValue(Grid.ColumnProperty, 0); 494 | BoardGrid.SetValue(Grid.RowSpanProperty, 1); 495 | BoardGrid.SetValue(Grid.ColumnSpanProperty, 2); 496 | 497 | waitIndicator.SetValue(Grid.RowProperty, 1); 498 | waitIndicator.SetValue(Grid.ColumnProperty, 0); 499 | waitIndicator.SetValue(Grid.RowSpanProperty, 1); 500 | waitIndicator.SetValue(Grid.ColumnSpanProperty, 2); 501 | 502 | Statistics.SetValue(Grid.RowProperty, 3); 503 | Statistics.SetValue(Grid.RowSpanProperty, 1); 504 | Statistics.SetValue(Grid.ColumnSpanProperty, 2); 505 | 506 | LayoutRoot.Margin = new Thickness(0, 0, 0, 72); 507 | LayoutRoot.RowDefinitions[0].Height = new GridLength(120); 508 | LayoutRoot.RowDefinitions[1].Height = new GridLength(460); 509 | 510 | for (int t = 0; t < Statistics.RowDefinitions.Count; t++) 511 | Statistics.RowDefinitions[t].Height = new GridLength(0); 512 | 513 | Statistics.ColumnDefinitions[0].Width = new GridLength(18); 514 | Statistics.ColumnDefinitions[1].Width = new GridLength(60, GridUnitType.Star); 515 | Statistics.ColumnDefinitions[2].Width = new GridLength(75, GridUnitType.Star); 516 | Statistics.ColumnDefinitions[3].Width = new GridLength(60, GridUnitType.Star); 517 | Statistics.ColumnDefinitions[4].Width = new GridLength(75, GridUnitType.Star); 518 | Statistics.ColumnDefinitions[5].Width = new GridLength(60, GridUnitType.Star); 519 | Statistics.ColumnDefinitions[6].Width = new GridLength(90, GridUnitType.Star); 520 | Statistics.ColumnDefinitions[7].Width = new GridLength(18); 521 | 522 | Statistics.RowDefinitions[0].Height = new GridLength(100, GridUnitType.Star); 523 | 524 | Statistics.Height = 64; 525 | 526 | MovesImage.SetValue(Grid.ColumnProperty, 1); 527 | MovesImage.SetValue(Grid.RowProperty, 0); 528 | 529 | EmptyImage.SetValue(Grid.ColumnProperty, 3); 530 | EmptyImage.SetValue(Grid.RowProperty, 0); 531 | 532 | GameTimeImage.SetValue(Grid.ColumnProperty, 5); 533 | GameTimeImage.SetValue(Grid.RowProperty, 0); 534 | 535 | Moves.SetValue(Grid.ColumnProperty, 2); 536 | Moves.SetValue(Grid.RowProperty, 0); 537 | 538 | Empty.SetValue(Grid.ColumnProperty, 4); 539 | Empty.SetValue(Grid.RowProperty, 0); 540 | 541 | GameTime.SetValue(Grid.ColumnProperty, 6); 542 | GameTime.SetValue(Grid.RowProperty, 0); 543 | } 544 | } 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /SudokuMaster/MemoryDiagnosticsControl.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011-2014 Microsoft Mobile. 3 | */ 4 | 5 | using System; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Controls.Primitives; 9 | using System.Windows.Media; 10 | using System.Windows.Threading; 11 | using Microsoft.Phone.Info; 12 | 13 | namespace SudokuMaster.Debug 14 | { 15 | /// 16 | /// Helper class for showing current memory usage. 17 | /// Run only in debug mode. 18 | /// 19 | 20 | public static class MemoryDiagnosticsControl 21 | { 22 | static Popup _popup; 23 | static TextBlock _currentMemoryBlock; 24 | static DispatcherTimer _timer; 25 | static bool _forceGc; 26 | 27 | /// 28 | /// Show the memory counter 29 | /// 30 | /// Whether or not to do automatic garbage collection each tick 31 | 32 | public static void Start(bool forceGc) 33 | { 34 | _forceGc = forceGc; 35 | 36 | CreatePopup(); 37 | CreateTimer(); 38 | ShowPopup(); 39 | StartTimer(); 40 | } 41 | 42 | /// 43 | /// Stop the memory counter 44 | /// 45 | 46 | public static void Stop() 47 | { 48 | HidePopup(); 49 | StopTimer(); 50 | } 51 | 52 | /// 53 | /// Show the popup 54 | /// 55 | 56 | static void ShowPopup() 57 | { 58 | _popup.IsOpen = true; 59 | } 60 | 61 | static void StartTimer() 62 | { 63 | _timer.Start(); 64 | } 65 | 66 | static void CreateTimer() 67 | { 68 | if (_timer != null) 69 | return; 70 | 71 | _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) }; 72 | _timer.Tick += TimerTick; 73 | } 74 | 75 | 76 | static void TimerTick(object sender, EventArgs e) 77 | { 78 | // call Garbage collector before getting memory usage 79 | if (_forceGc) 80 | GC.Collect(); 81 | var mem = (long)DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage"); 82 | _currentMemoryBlock.Text = string.Format("{0:N}", mem / 1024); 83 | } 84 | 85 | static void CreatePopup() 86 | { 87 | 88 | if (_popup != null) 89 | 90 | return; 91 | _popup = new Popup(); 92 | var fontSize = (double)Application.Current.Resources["PhoneFontSizeSmall"] - 2; 93 | var foreground = (Brush)Application.Current.Resources["PhoneForegroundBrush"]; 94 | var sp = new StackPanel { Orientation = Orientation.Horizontal, Background = (Brush)Application.Current.Resources["PhoneSemitransparentBrush"] }; 95 | _currentMemoryBlock = new TextBlock { Text = "---", FontSize = fontSize, Foreground = foreground }; 96 | sp.Children.Add(new TextBlock { Text = "Mem(kB): ", FontSize = fontSize, Foreground = foreground }); 97 | sp.Children.Add(_currentMemoryBlock); 98 | sp.RenderTransform = new CompositeTransform { Rotation = 90, TranslateX = 480, TranslateY = 420, CenterX = 0, CenterY = 0 }; 99 | _popup.Child = sp; 100 | } 101 | 102 | static void StopTimer() 103 | { 104 | _timer.Stop(); 105 | } 106 | 107 | static void HidePopup() 108 | { 109 | _popup.IsOpen = false; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /SudokuMaster/NumberSelection.xaml: -------------------------------------------------------------------------------- 1 |  4 | 14 | 15 | 16 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |