master
Mitja 3 years ago
commit d874a01da3
  1. 25
      .dockerignore
  2. 1
      .gitignore
  3. 3
      README.md
  4. 3
      SolverWebApp/.gitignore
  5. 31
      SolverWebApp/Controllers/SolutionController.cs
  6. 21
      SolverWebApp/Dockerfile
  7. 21
      SolverWebApp/Model/Solution.cs
  8. 26
      SolverWebApp/Pages/Error.cshtml
  9. 27
      SolverWebApp/Pages/Error.cshtml.cs
  10. 55
      SolverWebApp/Pages/Index.cshtml
  11. 20
      SolverWebApp/Pages/Index.cshtml.cs
  12. 17
      SolverWebApp/Pages/Shared/_Layout.cshtml
  13. 3
      SolverWebApp/Pages/_ViewImports.cshtml
  14. 3
      SolverWebApp/Pages/_ViewStart.cshtml
  15. 33
      SolverWebApp/Program.cs
  16. 34
      SolverWebApp/Properties/launchSettings.json
  17. 19
      SolverWebApp/SolverWebApp.csproj
  18. 9
      SolverWebApp/appsettings.Development.json
  19. 9
      SolverWebApp/appsettings.json
  20. 55
      SolverWebApp/wwwroot/css/sudoku-solver.css
  21. BIN
      SolverWebApp/wwwroot/favicon.bmp
  22. BIN
      SolverWebApp/wwwroot/favicon.ico
  23. 185
      SolverWebApp/wwwroot/js/sudoku-solver.js
  24. 34
      SolverWebApp/wwwroot/lib/dx/dx.all.js
  25. 23038
      SolverWebApp/wwwroot/lib/dx/dx.light.css
  26. 2
      SolverWebApp/wwwroot/lib/dx/jquery.min.js
  27. 25
      SudokuSolver.sln
  28. 10
      docker-compose.yml

@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

1
.gitignore vendored

@ -0,0 +1 @@
.vs/

@ -0,0 +1,3 @@
# Sudoku-Solver
Ein Sudoku-Solver für i2solutions GmbH

@ -0,0 +1,3 @@
bin/
obj/
*.csproj.user

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Mvc;
using SolverWebApp.Model;
namespace SolverWebApp.Controllers;
[ApiController]
public class SolutionController : Controller
{
// Retrieves all solutions from the database
[Route("solution")]
public ActionResult Index()
{
using (var db = new SolutionContext())
{
var solutions = db.Solutions.ToList();
return Json(solutions);
}
}
[HttpPost]
[Route("solution")]
public ActionResult StoreSolution(Solution solution)
{
using (var db = new SolutionContext())
{
var ee = db.Solutions.Add(solution);
db.SaveChanges();
}
return NoContent();
}
}

@ -0,0 +1,21 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["SolverWebApp/SolverWebApp.csproj", "SolverWebApp/"]
RUN dotnet restore "SolverWebApp/SolverWebApp.csproj"
COPY . .
WORKDIR "/src/SolverWebApp"
RUN dotnet build "SolverWebApp.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "SolverWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "SolverWebApp.dll"]

@ -0,0 +1,21 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
namespace SolverWebApp.Model;
public class SolutionContext : DbContext
{
public DbSet<Solution> Solutions { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=host.docker.internal; Initial Catalog=sudoku; User Id=SA; Password=i2suPers3cret.; TrustServerCertificate=True");
}
}
public class Solution
{
public int Id { get; set; }
public DateTime SolvedAt { get; set; } = DateTime.Now;
public string Data { get; set; }
}

@ -0,0 +1,26 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
namespace SolverWebApp.Pages
{
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

@ -0,0 +1,55 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
<div class="box">
<table id="sudoku_board">
<tbody>
@for(int j = 0; j < 3; j++)
{
<tr>
@for(int i = 0; i < 3; i++)
{
<td></td>
<td></td>
<td class="cell_separator"></td>
}
</tr>
<tr>
@for(int i = 0; i < 3; i++)
{
<td></td>
<td></td>
<td class="cell_separator"></td>
}
</tr>
<tr class="sudoku_row_separator">
@for(int i = 0; i < 3; i++)
{
<td></td>
<td></td>
<td class="cell_separator"></td>
}
</tr>
}
</tbody>
</table>
</div>
<p class="box" id="message"></p>
<div class="box">
<button type="button" onclick="clear_board()">Clear</button>
<button type="button" onclick="fill_default()">Preset 1</button>
<button type="button" onclick="solve()">Solve</button>
<button type="button" onclick="show_all_solutions()">Update Solution Table</button>
</div>
<div class="dx-viewport">
<div id="allsolutions"></div>
</div>

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace SolverWebApp.Pages
{
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sudoku Solver by QubiCode</title>
<link rel="stylesheet" href="~/lib/dx/dx.light.css">
<link rel="stylesheet" href="~/css/sudoku-solver.css" asp-append-version="true">
</head>
<body>
@RenderBody()
<script src="~/lib/dx/jquery.min.js"></script>
<script src="~/lib/dx/dx.all.js"></script>
<script src="~/js/sudoku-solver.js" asp-append-version="true"></script>
</body>
</html>

@ -0,0 +1,3 @@
@using SolverWebApp
@namespace SolverWebApp.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

@ -0,0 +1,33 @@
using SolverWebApp.Model;
using System.Net.Security;
using System.Net;
using System.Security.Cryptography.X509Certificates;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
using (var db = new SolutionContext())
{
db.Database.EnsureCreated();
}
app.Run();

@ -0,0 +1,34 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5151"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true
}
},
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:48904",
"sslPort": 0
}
}
}

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Content Include="Controllers\SolutionController.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
</ItemGroup>
</Project>

@ -0,0 +1,9 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

@ -0,0 +1,55 @@
html, body {
width: 100%;
height: 100%;
font-family: Verdana, Arial;
}
body {
margin: 0;
background: #f0f0f0;
}
.box {
margin: 1em 0;
width: 100%;
display:flex;
align-items: center;
justify-content: center;
}
#sudoku_board {
font-size: 5vmin;
border-top: 1vmin solid #444;
border-left: 1vmin solid #444;
border-collapse: collapse;
}
#sudoku_board tr {
border-bottom: 0.2vmin solid #444;
border-right: 1vmin solid #444;
}
.sudoku_row_separator {
border-bottom: 1vmin solid #444 !important;
}
#sudoku_board td {
width: 9vmin;
height: 9vmin;
border-right: 0.2vmin solid #444;
}
.cell_separator {
border-right: 1vmin solid #444 !important;
}
#sudoku_board {
background: #fff;
}
#sudoku_board td {
overflow: hidden;
text-align: center;
transition: all 0.25s;
}
#sudoku_board td:focus {
background: rgba(0, 0, 255, .1);
outline: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,185 @@
window.onload = setup
var cells_ordered = null
function setup() {
let table = document.getElementById('sudoku_board')
cells_ordered = table.getElementsByTagName('td')
console.log(cells_ordered.length)
for (let c = 0; c < 81; c++){
let cell = cells_ordered[c]
cell.addEventListener("keypress",check_cell_content)
cell.setAttribute("contenteditable","")
cell.innerHTML = '<br>' //Moves caret position to vertical center
}
show_all_solutions()
}
function fill_default() {
$('#message').text('')
publish_board([
[9,NaN,5,NaN,NaN,NaN,3,NaN,NaN],
[NaN,6,NaN,NaN,NaN,1,NaN,5,NaN],
[3,2,1,9,NaN,NaN,NaN,NaN,NaN],
[NaN,9,2,5,NaN,NaN,4,1,3],
[NaN,5,7,4,NaN,NaN,NaN,NaN,NaN],
[NaN,1,NaN,NaN,NaN,2,NaN,NaN,5],
[NaN,7,NaN,NaN,NaN,9,1,NaN,6],
[NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN],
[NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN]
])
}
function clear_board() {
$('#message').text('')
for (let row = 0; row < 9; row++)
for (let column = 0; column < 9; column++)
publish_cell(row, column, NaN)
}
function check_cell_content(e) {
console.log(e.key)
let content = parseInt(e.key)
console.log(content)
if ((content <= 0) || isNaN(content) || e.target.innerText.trim().length > 0){
e.preventDefault()
}
}
function get_data() {
let table = document.getElementById('sudoku_board')
let cells_ordered = table.getElementsByTagName('td')
let sudoku_board = [[],[],[],[],[],[],[],[],[]]
for (let cell = 0;cell<81;cell++){
let column = cell%9
let row = Math.floor(cell/9)
let value = parseInt(cells_ordered[cell].textContent.trim())
sudoku_board[row][column] = value
}
return sudoku_board
}
function publish_cell(row, column, value) {
cells_ordered[row*9 + column].innerHTML = "" + (isNaN(value) ? "<br>" : value)
}
function publish_board(board) {
for (let row = 0; row < 9; row++)
for (let column = 0; column < 9; column++)
publish_cell(row, column, board[row][column])
}
function set_cell(board, row, column, value) {
board[row][column] = value
publish_cell(row, column, value)
}
function solve_step(board, cell) {
let column = cell%9
let row = Math.floor(cell/9)
if(cell > 80)
return true
if(!isNaN(board[row][column]))
return solve_step(board, cell + 1)
for(let nr = 1;nr<=9;nr++) {
if(check_cell(board, row, column, nr)) {
set_cell(board, row, column, nr)
if(solve_step(board, cell + 1)) {
return true;
} else {
set_cell(board, row, column, NaN)
}
}
}
return false
}
function board_to_string(board) {
let ret = ""
for (let row = 0; row < 9; row++)
for (let column = 0; column < 9; column++)
ret += isNaN(board[row][column]) ? "0" : "" + board[row][column]
return ret
}
function store_board(board) {
$.ajax({
url: '/solution',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ "Data": board_to_string(board) }),
success: function () {
$('#message').text('Stored solution to database')
show_all_solutions()
},
error: function () {
$('#message').text('Error on saving the solution to the database.')
}
})
}
function solve() {
$('#message').text('')
let board = get_data()
if (!check_board(board)) {
$('#message').text("The numbers on the board are in conflict with the rules of Sudoku")
return
}
if (!solve_step(board, 0)) {
$('#message').text("Unable to solve this Sudoku")
return
}
store_board(board)
}
function show_all_solutions() {
jQuery.get("/solution", function (data) {
new DevExpress.ui.dxDataGrid(document.getElementById("allsolutions"), {
dataSource: data,
columnAutoWidth: true
});
}).fail(function (e) { console.warn("fetching solution failed: ", e) })
}
function check_cell(board, row, col, value) {
// if already present on the column, not acceptable
for (let r = 0; r < 9; ++r)
if (board[r][ col] == value) return false;
// if already present on the row, not acceptable
for (let c = 0; c < 9; ++c)
if (board[row][ c] == value) return false;
// if already present in the same 3x3 square, also not acceptable
let r1 = Math.floor(row / 3) * 3;
let c1 = Math.floor(col / 3) * 3;
for (let r = r1; r < r1 + 3; ++r) {
for (let c = c1; c < c1 + 3; ++c) {
if (board[r][c] == value) return false;
}
}
return true;
}
function check_board(board) {
for (let r = 0; r < 9; ++r)
for (let c = 0; c < 9; ++c) {
let value = board[r][c]
if(isNaN(value))
continue
board[r][c] = NaN
let res = check_cell(board,r,c, value)
board[r][c] = value
if(!res)
return false
}
return true
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33103.184
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SolverWebApp", "SolverWebApp\SolverWebApp.csproj", "{C46F3015-62EC-45B1-97AF-1D6A6EEDD817}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C46F3015-62EC-45B1-97AF-1D6A6EEDD817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C46F3015-62EC-45B1-97AF-1D6A6EEDD817}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C46F3015-62EC-45B1-97AF-1D6A6EEDD817}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C46F3015-62EC-45B1-97AF-1D6A6EEDD817}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C51FF7B2-0158-4180-A278-70030C3C2D1F}
EndGlobalSection
EndGlobal

@ -0,0 +1,10 @@
services:
db:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
ACCEPT_EULA: "1"
MSSQL_SA_PASSWORD: i2suPers3cret.
ports:
- 127.0.0.1:1433:1433
expose:
- "1433"
Loading…
Cancel
Save