Click here to Skip to main content
15,885,885 members
Articles / Web Development / HTML
Article

MVC Sudoku 4

Rate me:
Please Sign up or sign in to vote.
4.47/5 (6 votes)
19 Aug 2016CPOL9 min read 14.6K   676   8   2
An ASP.NET MVC 4 Implementation of Sudoku, with a Board Builder

Image 1

Introduction

When I first decided to delve into ASP.NET MVC, my creative side was inspired by Han Hung's Sudoku implementation written in VB.NET. His great write up, complete with flowcharts, can be found here. My first thought was that I could wade into the waters of MVC by just porting his work from VB.NET to C#.

A few days later, I came to my senses. Trying to learn MVC through a port is a bit like trying to learn Hindi by watching Bollywood films; it'll only get you so far. So, I took the original inspiration from Han's app and went in a different direction. The result is this web-based implementation of Sudoku using MVC 4.

This story is the only excuse I can offer for adding yet another Sudoku game to the CodeProject library.

The Project

Even though it doesn't have the same feature set as Visual Studio Express, I'm a big fan of the open-source IDE SharpDevelop. I developed this ASP .NET site in SharpDevelop, using IIS Express for hosting. If you download this project and run it, be sure to create a virtual directory for it on your web server. I randomly chose to use port 7070.

For this project, I use the traditional Model-View-Controller pattern. There are a lot of variants of MVC, including Model-View-Adapter, Model-View-Presenter, and Model-View-Whatever; but I stuck with MVC. In a strict MVC pattern, the Controller populates the Model, the Model exposes data to the View, and the View communicates user actions to the Controller. This diagram from Wikipedia shows the interactions of the MVC pieces:

MVC Diagram

MVC is a pattern for exposing data and handling user responses in the Presentation Layer. The Presentation Layer is not meant to do the heavy lifting of the application. I placed my application logic in a Service Layer, and my logic for persisting data in a Data Layer. MSDN has a great article on how the different layers should be organized in an MVC application.

The Domain and View Models

That's enough design theory. Let's look at the code. The first item I created was a Cell class.

C#
public class Cell
{
    public int XCoordinate { get; set; }
    public int YCoordinate { get; set; }
    public int BlockNumber { get; set; }
    [Range(1, Constants.BoardSize, ErrorMessage = Constants.CellErrorMessage)]
    public int? Value { get; set; }

    public Cell()
    {
        Value = null;
    }
}

The Cell class is my domain model. In order to keep track of where a Cell ends up in the Cartesian-plane board, I used x- and y-coordinates. At some point in the game processing, I'll need to know which 3x3 block a cell belongs to. After some experimentation, I realized that giving a Cell a block number was the easiest way to handle and validate boards. The end result is similar to the approach that Han Hung took in his Sudoku project.

Since a standard 9x9 Sudoku board can be blank or contain the numbers 1 through 9, I made the Cell's value a Nullable int. Here the NULL value represents blank on the board. I was able to limit the non-NULL values to 1 through 9 by using a data validation attribute. The "9" in 1 through 9 is defined in Constants.cs, along with the error message for Cell.cs's validation.

Using the Cell class I was able to create a view model that I called FullBoard.

C#
public class FullBoard
{
    public List<Cell> BoardList { get; set; }
    public int BoardNumber { get; set; }
    public PuzzleStatus Status { get; set; }

    public FullBoard()
    {
        BoardList = new List<Cell>();
        Status = PuzzleStatus.Normal;
    }
}

The board is set up to hold a list of Cell elements. Even though it is more natural to think of the board as a two-dimensional array, lists are easier to pass around in ASP.NET. I also added a status field to the board. Models typically don't have any complex processing, so all of the processing related to setting up a new board or determining a puzzle's status has been moved to the Service Layer.

The Views

The FullBoard model is used by the two main views: GameView and BuilderView. The GameView is used for playing a preloaded game. The BuilderView is used to build your own game, then save it so it can be played in the future. I also have an Index page, but it is only used to call the screens hosting the GameView and BuilderView. Here is the GameView.

ASP.NET
@model MVCSuDoku4.Model.FullBoard

<body>
	<div>
		<table>
			<tr>
				<td valign="top" rowspan="2">
					@Html.Partial("_StatusView")
				</td>
				<td rowspan="2">
					<center>
						@{ 
							ViewData["updateAction"] = "UpdateGame";
							ViewData["updateView"] = "Game";
						}
						@Html.Partial("_BoardView")
					</center>
				</td>
				<td valign="top">
					<div style="height: 5px"></div>
					<button type="button" class="bigbutton" onclick="location.href='@Url.Action("Index", "Home")'">Back to Menu</button><br/>
				</td>
			</tr>
			<tr>
				<td valign="bottom">
					@Html.Partial("_ResetButtonView")
					<button type="button" class="bigbutton" onclick="location.href='@Url.Action("NewGame", "Game")'">New Game</button><br/>
					@{ 
						ViewData["saveAction"] = "SaveGame";
						ViewData["saveView"] = "Game";
					}
					@Html.Partial("_SaveButtonView")
					<button type="button" class="bigbutton" onclick="location.href='@Url.Action("LoadGame", "Game")'">Load Saved Game</button>
					<br/><br/>
				</td>
			</tr>
		</table>
	</div>
</body>

Since some of the elements are the same in each view, particularly the status, board and save button, I created partial views for these elements. Partial views produce a control or UI element that can be used across multiple views. Here is the _BoardView partial view.

ASP.NET
{
    int boardSize = (int)(ViewData["BoardSize"]);
    int blockSize = (int)(ViewData["BlockSize"]);
    var tbConfig = new { maxlength="1", size="1", autocomplete="off", @onkeyup = "SubmitValidCellValue(event)" };

    @Html.HiddenFor(m => m.BoardNumber)

    for (int x = 0; x < boardSize; x++)
    {
        if ((x != 0) && (x % blockSize == 0))
        {
            @:<br/>
        }

        for (int y = 0; y < boardSize; y++)
        {
            if ((y != 0) && (y % blockSize == 0))
            {
                @: 
            }
            @Html.TextBoxFor(m => m.BoardList[x * boardSize + y].Value, tbConfig)
            @Html.HiddenFor(m => m.BoardList[x * boardSize + y].XCoordinate)
            @Html.HiddenFor(m => m.BoardList[x * boardSize + y].YCoordinate)
            @Html.HiddenFor(m => m.BoardList[x * boardSize + y].BlockNumber)
        }
        <br/>
    }
}

The Board partial view has some clever, but simple JavaScript that will be discussed below.

For my Save Button partial view, I wanted different behavior depending on which parent view the button is created on. I used ViewData to pass a parameter from the parent view to the partial view. (The _BoardView.cshtml shown above follows this same pattern.)

ASP.NET
@{
    string saveActionName = ViewData["saveAction"].ToString();
    string saveView = ViewData["saveView"].ToString();
}
@using (Html.BeginForm(@saveActionName, @saveView, FormMethod.Post, new { name = "saveForm", autocomplete="off" }))
{
    for (int i = 0; i < Model.BoardList.Count; i++)
    {
        @Html.HiddenFor(m => m.BoardList[i].Value)
        @Html.HiddenFor(m => m.BoardList[i].XCoordinate)
        @Html.HiddenFor(m => m.BoardList[i].YCoordinate)
        @Html.HiddenFor(m => m.BoardList[i].BlockNumber)
    }
    @Html.HiddenFor(m => m.BoardNumber)

    <button type="button" class="bigbutton" name="action" onclick="document.saveForm.submit()">Save Game</button><br/>
}

The Controllers

The views and partial views send actions to the controllers. My controllers are moderately simple. They mostly organize data and pass it to the Service layer classes, where the bulk of the logic is held. I have three controllers for my three views: A HomeController, a GameController, and a BuilderController. Here are the main methods in the GameController.

C#
public class GameController : Controller
{
    ...

    public ActionResult NewGame()
    {
        FullBoard board = new FullBoard() { BoardList = puzzleService.SetupBoard() };
        int puzzleNumber;
        puzzleLoader.LoadNewPuzzle(board.BoardList, out puzzleNumber);
        board.BoardNumber = puzzleNumber;
        return View("GameView", board);
    }

    public ActionResult UpdateGame(FullBoard board)
    {
        board.Status = puzzleService.GetPuzzleStatus(board.BoardList);
        return View("GameView", board);
    }

    public ActionResult ResetGame(FullBoard board)
    {
        board.BoardList = puzzleService.SetupBoard();
        puzzleLoader.ReloadPuzzle(board.BoardList, board.BoardNumber);
        return View("GameView", board);
    }

    public ActionResult SaveGame(FullBoard board)
    {
        puzzleSaver.SaveGame(board.BoardList, board.BoardNumber);
        board.Status = puzzleService.GetPuzzleStatus(board.BoardList);
        return View("GameView", board);
    }

    public ActionResult LoadGame()
    {
        FullBoard board = new FullBoard() { BoardList = puzzleService.SetupBoard() };
        int puzzleNumber;
        puzzleLoader.LoadSavedPuzzle(board.BoardList, out puzzleNumber);
        board.Status = puzzleService.GetPuzzleStatus(board.BoardList);
        board.BoardNumber = puzzleNumber;
        return View("GameView", board);
    }
}

One thing worth noting is that my BuilderController has two SavePuzzleSetup methods, one with an HttpPost attribute and one with HttpGet. HttpPost is used to submit data from the view to the controller; HttpGet is used to return data to the view. Together, the two methods receive the model, save the board and assign a BoardNumber (if the board was saved for the first time), then pass the update back to the view.

C#
public class BuilderController : Controller
{
    ...

    [HttpPost]
    public ActionResult SavePuzzleSetup(FullBoard board)
    {
        int puzzleNumber = board.BoardNumber;
        puzzleSaver.SavePuzzleSetup(board.BoardList, ref puzzleNumber);
        board.BoardNumber = puzzleNumber;
        board.Status = puzzleService.GetPuzzleStatus(board.BoardList);

        // Return the model's updated BoardNumber by redirecting to an HttpGet method
        TempData["Board"] = board;
        return RedirectToAction("SavePuzzleSetup");
    }

    [HttpGet]
    public ActionResult SavePuzzleSetup()
    {
        return View("BuilderView", (FullBoard)TempData["Board"]);
    }
}

The two SavePuzzleSetup methods make use of the TempDataDictionary by storing FullBoard in TempData. The HttpPost method stores the updated model in the TempDataDictionary, then makes a redirect call to the HttpGet method. The HttpGet method, in turn, reads the model from the TempDataDictionary and passes the model back to the view. The TempDataDictionary was made for scenarios just like this, where data needs to be stored for a very short amount of time. (See Rachel Appel's blog on this topic.)

The Service Layer

The controllers handle user actions by calling complex methods which reside in the Service Layer. The GameView and BuilderView both call methods exposed by the PuzzleService class, which creates a board and determines a puzzle's status. Even though SetupBoard() is always invoked when the FullBoard view model is constructed, I moved it to the Service Layer because the same logic would apply even if I were creating a Sudoku app that did not use MVC (for example, if I created the game using WPF).

C#
public class PuzzleService : IPuzzleService
{
    public List<Cell> SetupBoard()
    {
        List<Cell> board = new List<Cell>();

        for (int x = 0; x < Constants.BoardSize; x++)
        {
            for (int y = 0; y < Constants.BoardSize; y++)
            {
                Cell newCell = new Cell()
                {
                    XCoordinate = x + 1,
                    YCoordinate = y + 1,
                    BlockNumber = Constants.BlockSize * (x / Constants.BlockSize) + (y / Constants.BlockSize) + 1
                };
                board.Add(newCell);
            }
        }

        return board;
    }

The design of Cell made the validation very simple since I can easily group rows, columns, and blocks. I can validate each group with a couple of Linq statements. Checking for duplicates in a row is easy, but it's even easier to tell if a board is full. Note that the status can only be set to "Complete" if the board is valid.

C#
    ...

    private static bool AreRowsValid(List<Cell> cellList)
    {
        bool isValid = true;
        cellList.GroupBy(c => c.XCoordinate).Select(g => g.ToList()).ToList().ForEach(s => isValid &= IsValueUniqueInSet(s));
        return isValid;
    }

    private static bool AreColumnsValid(List<Cell> cellList)
    {
        bool isValid = true;
        cellList.GroupBy(c => c.YCoordinate).Select(g => g.ToList()).ToList().ForEach(s => isValid &= IsValueUniqueInSet(s));
        return isValid;
    }

    private static bool AreBlocksValid(List<Cell> cellList)
    {
        bool isValid = true;
        cellList.GroupBy(c => c.BlockNumber).Select(g => g.ToList()).ToList().ForEach(s => isValid &= IsValueUniqueInSet(s));
        return isValid;
    }

    private static bool IsValueUniqueInSet(List<Cell> cellGroup)
    {
        // Validate that each non-NULL value in this group is unique.  Ignore NULL values.
        return cellGroup.Where(c => c.Value.HasValue).GroupBy(c => c.Value.Value).All(g => g.Count() <= 1);
    }

    // Must be called after IsBoardValid().  A board can be completely filled in, but invalid.
    private static bool IsPuzzleComplete(List<Cell> cellList)
    {
        return cellList.All(c => c.Value.HasValue);
    }
}

The controllers also call the XDocPuzzleLoader and XDocPuzzleSaver classes. These classes convert the board's list of cells into XML, and vice versa.

C#
public class XDocPuzzleLoader : IPuzzleLoader
{
    ...

    public void ReloadPuzzle(List<Cell> cellList, int puzzleNumber)
    {
        LoadPuzzleFromSetupXDoc(puzzleNumber, cellList);
    }

    ...

    private void LoadPuzzleFromSetupXDoc(int puzzleNumber, List<Cell> cellList)
    {
        XDocument puzzleSetupXDoc = xDocPuzzleRepository.LoadPuzzleSetupXDoc();
        XElement x = puzzleSetupXDoc.Descendants("Puzzle").First(b => (int)b.Element("Number") == puzzleNumber);
        LoadCellListFromPuzzleXElement(x, cellList);
    }

    private void LoadCellListFromPuzzleXElement(XElement puzzleXElement, List<Cell> cellList)
    {
        var y = puzzleXElement.Descendants("Cells").Descendants("Cell").ToList();
        y.ForEach(c => cellList[(int)c.Attribute("index")].Value = (int?)c.Attribute("value"));
    }
}

The Data Layer

The XDocPuzzleLoader and XDocPuzzleSaver Service Layer classes have a dependency on a puzzle repository class, which resides in the Data Layer. The Data Layer class is injected into the service classes through constructor injection.

C#
public class XDocPuzzleLoader : IPuzzleLoader
{
    private IPuzzleRepository xDocPuzzleRepository = null;

    public XDocPuzzleLoader(IPuzzleRepository puzzleRepository)
    {
        xDocPuzzleRepository = puzzleRepository;
    }

    ...
}

Finally, the data is saved or loaded in the Data Layer classes. The Data Layer is responsible for saving and loading a game the user is playing. It also is used to create and persist new boards built by the user. Currently, the Data Layer contains one interface, IPuzzleRepository, and one class that implements that interface, XmlFilePuzzleRepository. Persisting data to an XML file was the easiest way to store and retrieve different boards.

C#
public class XmlFilePuzzleRepository : IPuzzleRepository
{
    ...

    public XDocument LoadPuzzleSetupXDoc()
    {
        return XDocument.Load(puzzleSetupXmlPath);
    }

    public XDocument LoadSavedGameXDoc()
    {
        return XDocument.Load(savedGameXmlPath);
    }

    public void SavePuzzleSetupXDoc(XDocument xDoc)
    {
        xDoc.Save(puzzleSetupXmlPath);
    }

    public void SaveSavedGameXDoc(XDocument xDoc)
    {
        xDoc.Save(savedGameXmlPath);
    }
}

Dependency Injection

The Data Layer classes shown above use dependendency injection (DI). For such a simple program why use DI? I am not a fan of using any pattern, DI included, simply for the sake of the pattern. However, by decoupling the service classes from the data classes, this pattern offers the flexibility to swap in different data repository classes. In the future I might want to save the data PostgreSQL or SQL Server. Maybe I'll tinker around with Cassandra. Well, maybe not with this application. But by accepting an interface in my Service Layer injection method, I have the flexibility to easily switch how I store the data.

To implement DI in a small to medium-sized project I usually use dependency injection by hand, which is sometimes pejoratively referred to as "poor man's dependency injection". However, the .NET Framework offers a simple and elegant alternative in the Unity Application Block containers. With, like, five lines of code, I was able to set up Unity for DI. OK - five lines is an exaggeration, but it was remarkably easy to create the container and factories! Just take a look at my code from UnityControllerFactory.cs and Global.asax.cs.

C#
public class UnityControllerFactory : DefaultControllerFactory
{
    private readonly IUnityContainer container;

    public UnityControllerFactory(IUnityContainer unityContainer)
    {
        container = unityContainer;
    }

    protected override IController GetControllerInstance(RequestContext context, Type controllerType)
    {
        return (controllerType == null) ? null : container.Resolve(controllerType) as IController;
    }
}
C#
// Note: The Global.asax.cs holds the MvcApplication class.
public class MvcApplication : HttpApplication
{
    ...

    private static void SetupUnityFactory()
    {
        IUnityContainer container = new UnityContainer();
        container.RegisterType<IPuzzleRepository, XmlFilePuzzleRepository>();
        container.RegisterType<IPuzzleLoader, XDocPuzzleLoader>();
        container.RegisterType<IPuzzleSaver, XDocPuzzleSaver>();
        container.RegisterType<IPuzzleService, PuzzleService>();

        UnityControllerFactory factory = new UnityControllerFactory(container);
        ControllerBuilder.Current.SetControllerFactory(factory);
    }
}

That's all there is to it. The DI framework has been set up.

Client-Side Validation

One more point bears mentioning. ASP.NET MVC is a framework built on top of ASP.NET. It does not change the fundamentals of how ASP.NET works. Even though MVC can inject JavaScript validation code, the C# controller code still executes on the server-side. As a result, whenever the user clicks a button on the UI or enters a number, the web page needs to do a round trip to process the user action. At the end of the round trip, the server performs a postback to the client, and the entire screen by default is redrawn.

This behavior raises the question, "When the user enters a number into a cell, when and how should the action be sent to the server-side controller code?" In order to update the board's status to indicate that a cell entry is invalid, or that the puzzle was successfully completed, I want to respond to each value entered into a cell. After a little experimenting, I found it best to trigger an event on the JavaScript onkeyup event (in the _BoardView partial view).

JavaScript
function SubmitValidCellValue(event) {
    var value = event.currentTarget.value;
    if ((value == "") || (1 <= value && value <= 9)) {
        document.updateForm.submit();
    } else {
        // If invalid, clear out value on screen
        event.currentTarget.value = "";
    }
}

This JavaScript function runs on the client-side to check that the input value is either blank or a single-digit number. Valid entries are then passed to the server-side to update the board and check the board's status. On a fast system, all of this takes place before the user is able to move the mouse and click in a different cell. This well-placed JavaScript function gives me exactly what I hoped for: simple and immediate client-side validation followed by a more complicated but almost unnoticeable server-side update to the board's status.

Conclusion

The end result, my friends, is the top selling, billion dollar grossing, critically acclaimed iStore app that ... Oh, wait. Scratch that last part. I was thinking about something else.

The end result is a standard Sudoku application which happens to use ASP.NET MVC yet still creates a user-friendly, playable experience. Enjoy!

History

Version 1.0 - First release to Code Project

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMultiple build errors Pin
MirandaJ12233-Mar-23 11:05
MirandaJ12233-Mar-23 11:05 
PraiseNice article and great way to learn by doing Pin
Mukul00319-Apr-17 21:43
professionalMukul00319-Apr-17 21:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.