#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:
Y si ponemos un valor incorrecto:
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