#retosMSDN: Solución al Reto 2 de Navidad - Creando una web para resolver Sudokus con Visual Studio

En el primer #retosMSDN de Navidad te propusimos crear una API REST de Sudokus que dado un tablero de Sudoku clásico de 9x9 completa o parcialmente relleno nos dijese si todos los números introducidos están en una posición válida. Aquí tienes su solución. En el segundo te propusimos crear un sitio web que consumiese esa API, y esta es la solución que nosotros hemos creado para este segundo reto. Esperamos que te hayas entretenido tanto como nosotros.

Nuestra solución

En esta ocasión hemos decidido hacer una web mediante ASP.NET y para la parte de front TypeScript de manera que tenemos parte de la lógica en cliente y otra parte en servidor (Esto nos puede servir si tenemos una serie de usuarios que interactúan con los diferentes sudokus, para añadir seguridad, etc…).

Este es el aspecto que tiene nuestra web:

image

Y si ponemos un valor incorrecto:

image

Para la parte de backend hemos seguido el patrón MVC de ASP.NET además de utilizar la inyección de dependencias con Unity, podemos observar un controllador llamado ‘HomeController’ la vista principal ‘Home/Index.cshtml’ , el modelo ‘Sudoku’ y dos servicios ‘HttpService’ para gestionar las llamadas que haremos a la API externa y ‘Local/SudokuService’ para gestionar lo relacionado con nuestro Sudoku.

HomeController

En esta clase inyectamos el servicio ‘SudokuService’ y tenemos dos métodos, uno para el Index y otro para la llamada que haremos desde JavaScript para validar el sudoku.

  1: public class HomeController : Controller
  2: {
  3:     private readonly SudokuService sudokuService;
  4:  
  5:     public HomeController(SudokuService sudokuService) {
  6:         this.sudokuService = sudokuService;
  7:     }
  8:  
  9:     public ActionResult Index()
  10:     {
  11:         return View();
  12:     }
  13:  
  14:     [HttpPost]
  15:     public async Task<ActionResult> IsValid(Sudoku sudoku)
  16:     {
  17:         var result = await this.sudokuService.IsValid(sudoku);
  18:         return Json(result);
  19:     }
  20: }

Sudoku

Un modelo básico para el tablero de nuestro sudoku

  1: public class Sudoku
  2: {
  3:     public int[][] Values { get; set; }
  4: }

 

Index.cshtml

Generamos el tablero de sudoku con el que interactuará el usuario.

  1: @{
  2:     ViewBag.Title = "Sudoku Test";
  3:     var cell = 0;
  4: }
  5:  
  6: <section class="page page-main">
  7:     <h1 class="page__title">Sudoku Maker</h1>
  8:  
  9:     <article class="sudoku">
  10:         @for (var i = 0; i < 3; i++){ // rows loop
  11:         <div class="sudoku__row">
  12:             @for (var j = 0; j < 3; j++)
  13:             { // columns loop
  14:             <div class="sudoku__column">
  15:                 @for (var k = 0; k < 9; k++)
  16:                 { // cells loop
  17:                     <div class="sudoku__cell js-cell" data-sk="@cell,@k" contenteditable="true"> </div>
  18:                 }
  19:             </div>
  20:             cell++;
  21:             }
  22:         </div>
  23:         }
  24:     </article>
  25: </section>
  26:  
  27: @Scripts.Render("~/Content/ts/Index.js")

SudokuService

Inyectamos ‘HttpService’ y hacemos la llamada a la API

  1: public class SudokuService
  2: {
  3:     const string baseURL = "https://microsoftsudokuweb.azurewebsites.net/api/sudoku/";
  4:     private readonly HttpService httpService;
  5:     public SudokuService(HttpService httpService)
  6:     {
  7:         this.httpService = httpService;
  8:     }
  9:  
  10:     public async Task<bool> IsValid(Sudoku sudoku) 
  11:     {
  12:         var response = await httpService.PostAsync(baseURL + "IsValidGame", sudoku);
  13:  
  14:         return JsonConvert.DeserializeObject<bool>(response);
  15:     }
  16: }

 

Para la parte de frontend hemos creado una clase simple de TypeScript con la que manejamos todo lo necesario para interactuar con el usuario en nuestro juego

  1: module index {
  2:     "use strict";
  3:  
  4:     class Sudoku {
  5:  
  6:         private container: any;
  7:         private board: number[][] = [];
  8:         private links: any = {};
  9:  
  10:         constructor(classContainer: string) {
  11:             // Get Dom elements
  12:             this.container = $('.' + classContainer);
  13:             this.links.isValid = $('.js-isvalid>a');
  14:  
  15:             // Bind events
  16:             this.bindEvents();
  17:  
  18:             // initialize board
  19:             for (var i: number = 0; i < 9; i++) {
  20:                 var arr = [];
  21:                 for (var j: number = 0; j < 9; j++) {
  22:                     arr[j] = 0;
  23:                     this.board[i] = arr;
  24:                 }
  25:             }
  26:         }
  27:  
  28:         private bindEvents() {
  29:             this.container.find('.js-cell')
  30:                 .on('blur', this.onBlurCell)
  31:                 .on('keyup', e => this.onChangeCell(e));
  32:  
  33:             this.links.isValid.on('click', e => this.onClickIsValid(e));
  34:         }
  35:  
  36:         private onBlurCell() {
  37:             // Add placeholder
  38:             if ($(this).text() == '') {
  39:                 $(this).text('')
  40:                 $(this).removeClass('js-has_number');
  41:             }
  42:         }
  43:  
  44:         private onChangeCell(e) {
  45:             // Check cell value
  46:             var $el = $(e.currentTarget);
  47:             $el.removeClass('js-has_number');
  48:             var val = parseInt($el.text(), 10);
  49:             if (val < 1 || val > 9 || isNaN(val)) {
  50:                 $el.text('');
  51:             } else {  
  52:                 $el.addClass('js-has_number');
  53:             }
  54:             var keys = $el.data('sk').split(',');
  55:             var val = parseInt($el.text(), 10);
  56:             this.board[keys[0]][keys[1]] = ($el.text() === '') ? 0 : val;
  57:             $el.blur();
  58:         }
  59:  
  60:         private sendRequest(url: string) {
  61:             $.ajax({
  62:                 url: '/Home/' + url,
  63:                 type: 'POST',
  64:                 data: JSON.stringify({
  65:                     Values: this.board,
  66:                 }),
  67:                 contentType: 'application/json; charset=utf-8',
  68:                 success: e => this.onSuccess(e),
  69:                 error: e => console.log(e)
  70:             });
  71:         }
  72:  
  73:         private onClickIsValid(e: Event) {
  74:             this.sendRequest('IsValid');
  75:         }
  76:  
  77:         private onSuccess(response) {
  78:             if (response) {
  79:                 this.container.removeClass('js-invalid');
  80:                 this.container.addClass('js-valid');
  81:             } else {
  82:                 this.container.removeClass('js-valid');
  83:                 this.container.addClass('js-invalid');
  84:             }
  85:         }
  86:     }
  87:     
  88:     window.onload = () => {
  89:         var sudoku = new Sudoku('sudoku');
  90:     }
  91:  
  92: }

El código completo lo puedes encontrar en esta solución de Visual Studio 2013 que puedes descargarte de GitHub. Y si tienes cualquier duda sobre la solución, no dudes en ponerte en contacto con nosotros en esmsdn@microsoft.com.

¿Sabías que empezar a trabajar con TypeScript es muy fácil?

_____________________________

Quique Fernández

Technical Evangelist Intern

@CKGrafico