711 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
		
		
			
		
	
	
			711 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| 
								 | 
							
								<html>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<head>
							 | 
						||
| 
								 | 
							
								</head>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<body>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									<div id="app" class="app"></div>
							 | 
						||
| 
								 | 
							
									<pre>
							 | 
						||
| 
								 | 
							
											TODO:
							 | 
						||
| 
								 | 
							
											 - if orange fields, no other selections should be possible than deselect.
							 | 
						||
| 
								 | 
							
										</pre>
							 | 
						||
| 
								 | 
							
									<template id="template_sudoku_container">
							 | 
						||
| 
								 | 
							
										<div style="width:640px;height:640px;font-size:1.2em;" class="container-content"></div>
							 | 
						||
| 
								 | 
							
									</template>
							 | 
						||
| 
								 | 
							
									<template id="template_sudoku_parser">
							 | 
						||
| 
								 | 
							
										<div style="width:100%;height:100%" class="parser-content"></div>
							 | 
						||
| 
								 | 
							
									</template>
							 | 
						||
| 
								 | 
							
									<template id="template_sudoku_cell">
							 | 
						||
| 
								 | 
							
										<div class="cell-content"></div>
							 | 
						||
| 
								 | 
							
									</template>
							 | 
						||
| 
								 | 
							
									<style type="text/css">
							 | 
						||
| 
								 | 
							
										.app {
							 | 
						||
| 
								 | 
							
											width: 400px;
							 | 
						||
| 
								 | 
							
											height: 400px;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										.cell-content {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											font-family: 'Courier New', Courier, monospace;
							 | 
						||
| 
								 | 
							
											user-select: none;
							 | 
						||
| 
								 | 
							
											-webkit-user-select: none;
							 | 
						||
| 
								 | 
							
											-moz-user-select: none;
							 | 
						||
| 
								 | 
							
											-ms-user-select: none;
							 | 
						||
| 
								 | 
							
											border: 1px solid #CCCCCC;
							 | 
						||
| 
								 | 
							
											text-align: center;
							 | 
						||
| 
								 | 
							
											height: 10%;
							 | 
						||
| 
								 | 
							
											width: 10%;
							 | 
						||
| 
								 | 
							
											float: left;
							 | 
						||
| 
								 | 
							
											display: flex;
							 | 
						||
| 
								 | 
							
											justify-content: center;
							 | 
						||
| 
								 | 
							
											align-items: center;
							 | 
						||
| 
								 | 
							
											font-size: calc(5px + 1vw);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										.sudoku-cell-selected {
							 | 
						||
| 
								 | 
							
											background-color: lightgreen;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										.sudoku-cell-invalid {
							 | 
						||
| 
								 | 
							
											color: red;
							 | 
						||
| 
								 | 
							
											font-weight: bold;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									</style>
							 | 
						||
| 
								 | 
							
									<script type="text/javascript">
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const game_size = 9;
							 | 
						||
| 
								 | 
							
										const container_template = document.getElementById('template_sudoku_container');
							 | 
						||
| 
								 | 
							
										const cell_template = document.getElementById("template_sudoku_cell");
							 | 
						||
| 
								 | 
							
										const sudokuParserTemplate = document.getElementById('template_sudoku_parser');
							 | 
						||
| 
								 | 
							
										const container = document.getElementById('app'); //.importNode(container_template.content, true);
							 | 
						||
| 
								 | 
							
										const _hashChars = "abcdefghijklmnopqrstuvwxyz\"':[]0123456789";
							 | 
						||
| 
								 | 
							
										function hashString(str) {
							 | 
						||
| 
								 | 
							
											let result = 0;
							 | 
						||
| 
								 | 
							
											for (let i = 0; i < str.length; i++) {
							 | 
						||
| 
								 | 
							
												result += (_hashChars.indexOf(str[i]) + 1) * (i * 40);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											return result;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										class State {
							 | 
						||
| 
								 | 
							
											number = 0
							 | 
						||
| 
								 | 
							
											cells = []
							 | 
						||
| 
								 | 
							
											selection = []
							 | 
						||
| 
								 | 
							
											selectionCount = 0
							 | 
						||
| 
								 | 
							
											_string = null
							 | 
						||
| 
								 | 
							
											_hash = null
							 | 
						||
| 
								 | 
							
											_json = null;
							 | 
						||
| 
								 | 
							
											constructor(arg) {
							 | 
						||
| 
								 | 
							
												if (isNaN(arg)) {
							 | 
						||
| 
								 | 
							
													this.number = arg.number;
							 | 
						||
| 
								 | 
							
													this.cells = arg.cells;
							 | 
						||
| 
								 | 
							
													this.selectCount = arg.selectionCount;
							 | 
						||
| 
								 | 
							
													this._hash = arg._hash;
							 | 
						||
| 
								 | 
							
													this._json = arg;
							 | 
						||
| 
								 | 
							
												} else {
							 | 
						||
| 
								 | 
							
													this.number = arg;
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											toJson() {
							 | 
						||
| 
								 | 
							
												if (this._json == null) {
							 | 
						||
| 
								 | 
							
													const json = {
							 | 
						||
| 
								 | 
							
														number: this.number,
							 | 
						||
| 
								 | 
							
														cells: this.cells,
							 | 
						||
| 
								 | 
							
														selectionCount: this.selectionCount,
							 | 
						||
| 
								 | 
							
														_hash: this.getHash()
							 | 
						||
| 
								 | 
							
													};
							 | 
						||
| 
								 | 
							
													this._json = json;
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												return this._json;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											getHash() {
							 | 
						||
| 
								 | 
							
												if (this._hash == null) {
							 | 
						||
| 
								 | 
							
													let started = false;
							 | 
						||
| 
								 | 
							
													let count = 0;
							 | 
						||
| 
								 | 
							
													let hash = this.cells.filter((cell) => {
							 | 
						||
| 
								 | 
							
														if (!started && cell.state[0] == 0 && cell.state[1] == 1)
							 | 
						||
| 
								 | 
							
															return false;
							 | 
						||
| 
								 | 
							
														started = true;;
							 | 
						||
| 
								 | 
							
														count++;
							 | 
						||
| 
								 | 
							
														return true;
							 | 
						||
| 
								 | 
							
													}).map(cell => {
							 | 
						||
| 
								 | 
							
														return cell.state;
							 | 
						||
| 
								 | 
							
													}).join('');
							 | 
						||
| 
								 | 
							
													this._hash = `${count}${hash}`;
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												return this._hash
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											equals(otherState) {
							 | 
						||
| 
								 | 
							
												if (otherState.selectionCount != this.selectionCount)
							 | 
						||
| 
								 | 
							
													return false;
							 | 
						||
| 
								 | 
							
												return otherState.getHash() == this.getHash();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											toString() {
							 | 
						||
| 
								 | 
							
												if (this._string == null) {
							 | 
						||
| 
								 | 
							
													this._string = JSON.stringify({
							 | 
						||
| 
								 | 
							
														number: this.number,
							 | 
						||
| 
								 | 
							
														selection: this.selection.map(cell => cell.toString()),
							 | 
						||
| 
								 | 
							
														cells: this.cells
							 | 
						||
| 
								 | 
							
													});
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												return this._string
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										};
							 | 
						||
| 
								 | 
							
										class Cell {
							 | 
						||
| 
								 | 
							
											row = 0;
							 | 
						||
| 
								 | 
							
											col = 0;
							 | 
						||
| 
								 | 
							
											initial = false;
							 | 
						||
| 
								 | 
							
											letter = null
							 | 
						||
| 
								 | 
							
											name = null
							 | 
						||
| 
								 | 
							
											options = [];
							 | 
						||
| 
								 | 
							
											value = 0;
							 | 
						||
| 
								 | 
							
											values = []
							 | 
						||
| 
								 | 
							
											element = null;
							 | 
						||
| 
								 | 
							
											app = null;
							 | 
						||
| 
								 | 
							
											selected = false;
							 | 
						||
| 
								 | 
							
											container = null;
							 | 
						||
| 
								 | 
							
											async solve(){
							 | 
						||
| 
								 | 
							
												this.app.pushState();
							 | 
						||
| 
								 | 
							
												let originalValues = this.values;
							 | 
						||
| 
								 | 
							
												let valid = false;
							 | 
						||
| 
								 | 
							
												this.selected = false;
							 | 
						||
| 
								 | 
							
												for(let i = 1; i < 10; i++){
							 | 
						||
| 
								 | 
							
													this.addNumber(i);
							 | 
						||
| 
								 | 
							
													if(this.validate())
							 | 
						||
| 
								 | 
							
														if(await this.app.solve())
							 | 
						||
| 
								 | 
							
															return true;
							 | 
						||
| 
								 | 
							
													this.value = 0;
							 | 
						||
| 
								 | 
							
													this.values = originalValues;
							 | 
						||
| 
								 | 
							
													
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												return false;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											getState() {
							 | 
						||
| 
								 | 
							
												return `${this.selected ? 1 : 0}${this.valid ? 1 : 0}`;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											toggleSelect() {
							 | 
						||
| 
								 | 
							
												this.selected = !this.selected;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if (this.selected) {
							 | 
						||
| 
								 | 
							
													this.select();
							 | 
						||
| 
								 | 
							
													//this.element.classList.add('sudoku-cell-selected');
							 | 
						||
| 
								 | 
							
												} else {
							 | 
						||
| 
								 | 
							
													this.deSelect();
							 | 
						||
| 
								 | 
							
													//this.element.classList.remove('sudoku-cell-selected');
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												this.update();
							 | 
						||
| 
								 | 
							
												return this.selected;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											async addNumber(value) {
							 | 
						||
| 
								 | 
							
												this.values.pop(value);
							 | 
						||
| 
								 | 
							
												this.values.push(value);
							 | 
						||
| 
								 | 
							
												this.value = Number(value);
							 | 
						||
| 
								 | 
							
												const _this = this;
							 | 
						||
| 
								 | 
							
												window.requestAnimationFrame(() => {
							 | 
						||
| 
								 | 
							
													this.element.textContent = this.value == 0 ? "" : String(this.value);
							 | 
						||
| 
								 | 
							
													
							 | 
						||
| 
								 | 
							
												})
							 | 
						||
| 
								 | 
							
												this.validate();
							 | 
						||
| 
								 | 
							
												//this.update();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											onClick() {
							 | 
						||
| 
								 | 
							
												//this.
							 | 
						||
| 
								 | 
							
												if (!this.initial)
							 | 
						||
| 
								 | 
							
													this.toggleSelect();
							 | 
						||
| 
								 | 
							
												//this.app.onCellClick(this)
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											deSelect() {
							 | 
						||
| 
								 | 
							
												this.selected = false;
							 | 
						||
| 
								 | 
							
												this.element.classList.remove('sudoku-cell-selected');
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											select() {
							 | 
						||
| 
								 | 
							
												
							 | 
						||
| 
								 | 
							
												this.selected = true;
							 | 
						||
| 
								 | 
							
												this.element.classList.add('sudoku-cell-selected');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											validateBox(){
							 | 
						||
| 
								 | 
							
												let startRow = this.row - this.row % (9 / 3);
							 | 
						||
| 
								 | 
							
												let startCol = this.col - this.col % (9 / 3);
							 | 
						||
| 
								 | 
							
								    for (let i = startRow; i < 9 / 3; i++) {
							 | 
						||
| 
								 | 
							
								        for (let j = startCol; j < 9 / 3; j++) {
							 | 
						||
| 
								 | 
							
								            let fieldIndex = (i * 9) + j;
							 | 
						||
| 
								 | 
							
											console.info(fieldIndex);
							 | 
						||
| 
								 | 
							
											if (this.app.cells[fieldIndex].value == this.value) {
							 | 
						||
| 
								 | 
							
								                return false;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
										return true
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
										return true;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											isValid() {
							 | 
						||
| 
								 | 
							
												const _this = this;
							 | 
						||
| 
								 | 
							
												this.valid = !this.value && (!this.app.cells.filter(cell => {
							 | 
						||
| 
								 | 
							
													return cell.value != 0 &&
							 | 
						||
| 
								 | 
							
														cell != _this &&
							 | 
						||
| 
								 | 
							
														(cell.row == _this.row || cell.col == _this.col) &&
							 | 
						||
| 
								 | 
							
														cell.value == _this.value;
							 | 
						||
| 
								 | 
							
												}).length && this.validateBox()); //&& !this.app.getBoxValues(this.name).indexOf(this.value) == -1);
							 | 
						||
| 
								 | 
							
												return this.valid;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											update() {
							 | 
						||
| 
								 | 
							
												if (this.selected)
							 | 
						||
| 
								 | 
							
													this.select()
							 | 
						||
| 
								 | 
							
												else
							 | 
						||
| 
								 | 
							
													this.deSelect()
							 | 
						||
| 
								 | 
							
												this.app.cells.forEach(cell => {
							 | 
						||
| 
								 | 
							
													cell.validate()
							 | 
						||
| 
								 | 
							
												})
							 | 
						||
| 
								 | 
							
												this.element.textContent = this.value ? String(this.value) : "";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											validate() {
							 | 
						||
| 
								 | 
							
												if (this.isValid() || !this.value) {
							 | 
						||
| 
								 | 
							
													this.element.classList.remove('sudoku-cell-invalid');
							 | 
						||
| 
								 | 
							
												} else {
							 | 
						||
| 
								 | 
							
													this.element.classList.add('sudoku-cell-invalid');
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												return this.valid;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											destructor() {
							 | 
						||
| 
								 | 
							
												//this.container.delete();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											constructor(app, row, col) {
							 | 
						||
| 
								 | 
							
												this.app = app;
							 | 
						||
| 
								 | 
							
												this.container = document.importNode(cell_template.content, true);
							 | 
						||
| 
								 | 
							
												this.row = row;
							 | 
						||
| 
								 | 
							
												this.col = col;
							 | 
						||
| 
								 | 
							
												this.selected = false;
							 | 
						||
| 
								 | 
							
												this.letter = "abcdefghi"[row];
							 | 
						||
| 
								 | 
							
												this.name = `${this.letter}${this.col}`
							 | 
						||
| 
								 | 
							
												this.value = 0;
							 | 
						||
| 
								 | 
							
												this.values = [];
							 | 
						||
| 
								 | 
							
												this.element = this.container.querySelector('.cell-content');
							 | 
						||
| 
								 | 
							
												this.valid = true;
							 | 
						||
| 
								 | 
							
												const _this = this;
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('click', (e) => {
							 | 
						||
| 
								 | 
							
													_this.onClick();
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('mousemove', (e) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													if (!_this.initial && e.buttons == 1)
							 | 
						||
| 
								 | 
							
														_this.select();
							 | 
						||
| 
								 | 
							
													else if (!_this.initial && e.buttons == 2)
							 | 
						||
| 
								 | 
							
														_this.deSelect();
							 | 
						||
| 
								 | 
							
													else
							 | 
						||
| 
								 | 
							
														_this.app.pushState()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('mouseexit', (e) => {
							 | 
						||
| 
								 | 
							
													if (!e.buttons) {
							 | 
						||
| 
								 | 
							
														//		_this.app.pushState();
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('contextmenu', (e) => {
							 | 
						||
| 
								 | 
							
													e.preventDefault();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													if (!_this.initial && _this.selected) {
							 | 
						||
| 
								 | 
							
														_this.deSelect();
							 | 
						||
| 
								 | 
							
													} else {
							 | 
						||
| 
								 | 
							
														_this.values.pop(_this.value);
							 | 
						||
| 
								 | 
							
														_this.addNumber(0);
							 | 
						||
| 
								 | 
							
														_this.deSelect();
							 | 
						||
| 
								 | 
							
														_this.update();
							 | 
						||
| 
								 | 
							
														//_this.app.deSelectAll();
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											redraw() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											render(container) {
							 | 
						||
| 
								 | 
							
												container.appendChild(this.container);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											toString() {
							 | 
						||
| 
								 | 
							
												return `${this.row}:${this.col}`
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										class SudokuParser {
							 | 
						||
| 
								 | 
							
											app = null
							 | 
						||
| 
								 | 
							
											element = null
							 | 
						||
| 
								 | 
							
											container = null
							 | 
						||
| 
								 | 
							
											blank = true
							 | 
						||
| 
								 | 
							
											content = ''
							 | 
						||
| 
								 | 
							
											size = 9
							 | 
						||
| 
								 | 
							
											constructor(app, container) {
							 | 
						||
| 
								 | 
							
												//this.container = container;
							 | 
						||
| 
								 | 
							
												this.app = app;
							 | 
						||
| 
								 | 
							
												this.container = document.importNode(sudokuParserTemplate.content, true);
							 | 
						||
| 
								 | 
							
												this.element = document.createElement('div');
							 | 
						||
| 
								 | 
							
												this.element.style.width = '90%';
							 | 
						||
| 
								 | 
							
												this.element.style.height = '50%';
							 | 
						||
| 
								 | 
							
												this.element.border = '1px solid #CCCCCC';
							 | 
						||
| 
								 | 
							
												this.content = '';
							 | 
						||
| 
								 | 
							
												this.cells = []
							 | 
						||
| 
								 | 
							
												this.element.contentEditable = true;
							 | 
						||
| 
								 | 
							
												this.element.textContent = 'Paste puzzle here';//this.container.querySelector('.parser-content');
							 | 
						||
| 
								 | 
							
												this.toggle();
							 | 
						||
| 
								 | 
							
												this.blank = true;
							 | 
						||
| 
								 | 
							
												const _this = this;
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('click', (e) => {
							 | 
						||
| 
								 | 
							
													if (_this.blank)
							 | 
						||
| 
								 | 
							
														_this.element.textContent = '';
							 | 
						||
| 
								 | 
							
													_this.blank = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('contextmenu', (e) => {
							 | 
						||
| 
								 | 
							
													if (_this.blank)
							 | 
						||
| 
								 | 
							
														_this.element.textContent = '';
							 | 
						||
| 
								 | 
							
													this.blank = false;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('input', (e) => {
							 | 
						||
| 
								 | 
							
													_this.element.innerHTML = this.element.textContent;
							 | 
						||
| 
								 | 
							
													_this.parseAndApply();
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('keyup', (e) => {
							 | 
						||
| 
								 | 
							
													//_this.parseAndApply();
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												container.appendChild(this.element);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											parseAndApply() {
							 | 
						||
| 
								 | 
							
												this.parse();
							 | 
						||
| 
								 | 
							
												this.apply();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											parse() {
							 | 
						||
| 
								 | 
							
												const content = this.element.textContent;
							 | 
						||
| 
								 | 
							
												const regex = /\d/g;
							 | 
						||
| 
								 | 
							
												const matches = [...content.matchAll(regex)];
							 | 
						||
| 
								 | 
							
												let row = 0;
							 | 
						||
| 
								 | 
							
												let col = 0;
							 | 
						||
| 
								 | 
							
												const cells = []
							 | 
						||
| 
								 | 
							
												const max = this.size * this.size;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												matches.forEach(match => {
							 | 
						||
| 
								 | 
							
													if (row * col == max) {
							 | 
						||
| 
								 | 
							
														return;
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													if (col == 9) {
							 | 
						||
| 
								 | 
							
														row++;
							 | 
						||
| 
								 | 
							
														col = 0;
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													if (row == 9) {
							 | 
						||
| 
								 | 
							
														row = 0;
							 | 
						||
| 
								 | 
							
														col = 0;
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													const digit = match[0];
							 | 
						||
| 
								 | 
							
													const cell = new Cell(this.app, row, col);
							 | 
						||
| 
								 | 
							
													cell.addNumber(digit);
							 | 
						||
| 
								 | 
							
													cell.initial = true;
							 | 
						||
| 
								 | 
							
													cells.push(cell);
							 | 
						||
| 
								 | 
							
													col++;
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.cells = cells;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											apply() {
							 | 
						||
| 
								 | 
							
												this.app.cells.forEach(cell => {
							 | 
						||
| 
								 | 
							
													cell.initial = false;
							 | 
						||
| 
								 | 
							
													cell.number = 0;
							 | 
						||
| 
								 | 
							
													cell.numbers = [];
							 | 
						||
| 
								 | 
							
													cell.addNumber(0);
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.cells.forEach(cell => {
							 | 
						||
| 
								 | 
							
													const appCell = this.app.getCellByName(cell.name);
							 | 
						||
| 
								 | 
							
													appCell.initial = cell.value != 0;
							 | 
						||
| 
								 | 
							
													appCell.addNumber(cell.value);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.toggle();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											toggle() {
							 | 
						||
| 
								 | 
							
												if (this.element.style.display == 'none') {
							 | 
						||
| 
								 | 
							
													this.element.innerHTML = 'Paste here your puzzle';
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												this.element.style.display = this.element.style.display != 'none' ? 'none' : 'block';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										class Sudoku {
							 | 
						||
| 
								 | 
							
											cells = [];
							 | 
						||
| 
								 | 
							
											game_size = 0;
							 | 
						||
| 
								 | 
							
											cell_count = 0;
							 | 
						||
| 
								 | 
							
											selectedCells = []
							 | 
						||
| 
								 | 
							
											container = null
							 | 
						||
| 
								 | 
							
											element = null
							 | 
						||
| 
								 | 
							
											states = []
							 | 
						||
| 
								 | 
							
											previousSelection = []
							 | 
						||
| 
								 | 
							
											state_number = 1
							 | 
						||
| 
								 | 
							
											parser = null;
							 | 
						||
| 
								 | 
							
											status = null;
							 | 
						||
| 
								 | 
							
											reset(){
							 | 
						||
| 
								 | 
							
												this.cells.forEach(cell=>{
							 | 
						||
| 
								 | 
							
													cell.values = []
							 | 
						||
| 
								 | 
							
													cell.selected = false;
							 | 
						||
| 
								 | 
							
													cell.addNumber(0);
							 | 
						||
| 
								 | 
							
												})	
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											loadSession() {
							 | 
						||
| 
								 | 
							
												const session = this.getSession();
							 | 
						||
| 
								 | 
							
												if (!session)
							 | 
						||
| 
								 | 
							
													return null;
							 | 
						||
| 
								 | 
							
												this.state_number = session.state_number;
							 | 
						||
| 
								 | 
							
												this.states = session.states.map(state => {
							 | 
						||
| 
								 | 
							
													return new State(state);
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.refreshState();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											
							 | 
						||
| 
								 | 
							
											async getEmptyCell(){
							 | 
						||
| 
								 | 
							
												return this.cells.filter(cell=>{
							 | 
						||
| 
								 | 
							
													if(cell.value == 0)
							 | 
						||
| 
								 | 
							
														return true
							 | 
						||
| 
								 | 
							
													return false 
							 | 
						||
| 
								 | 
							
												})[0]
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											async solve() {
							 | 
						||
| 
								 | 
							
												const cell = await this.getEmptyCell();
							 | 
						||
| 
								 | 
							
												if(!cell)
							 | 
						||
| 
								 | 
							
													return this.isValid();
							 | 
						||
| 
								 | 
							
												return await cell.solve();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											deleteSession(){
							 | 
						||
| 
								 | 
							
												localStorage.removeItem('session');
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											getSession() {
							 | 
						||
| 
								 | 
							
												const session = localStorage.getItem('session');
							 | 
						||
| 
								 | 
							
												if (!session) {
							 | 
						||
| 
								 | 
							
													return null
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												return JSON.parse(session);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											saveSession() {
							 | 
						||
| 
								 | 
							
												this.pushState();
							 | 
						||
| 
								 | 
							
												const states = this.states.map(state => {
							 | 
						||
| 
								 | 
							
													return state.toJson()
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												const session = {
							 | 
						||
| 
								 | 
							
													state_number: this.state_number,
							 | 
						||
| 
								 | 
							
													states: states
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												localStorage.setItem('session', JSON.stringify(session));
							 | 
						||
| 
								 | 
							
												//console.info('session saved');
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											getBoxValues(cell_name) {
							 | 
						||
| 
								 | 
							
												let values = this.cells.filter(cell => {
							 | 
						||
| 
								 | 
							
													return cell.name != cell_name && cell.name[0] == cell_name[0]
							 | 
						||
| 
								 | 
							
												}).map(cell => {
							 | 
						||
| 
								 | 
							
													return cell.value
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												return values;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											toggle() {
							 | 
						||
| 
								 | 
							
												this.container.style.display = this.container.style.display != 'none' ? 'none' : 'block';
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											toggleParser() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												//this.parser.toggle();
							 | 
						||
| 
								 | 
							
												this.deSelectAll();
							 | 
						||
| 
								 | 
							
												this.parser.toggle()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											constructor(container, game_size) {
							 | 
						||
| 
								 | 
							
												const _this = this;
							 | 
						||
| 
								 | 
							
												this.container = container
							 | 
						||
| 
								 | 
							
												this.element = container
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.parser = new SudokuParser(this, this.container);
							 | 
						||
| 
								 | 
							
												this.game_size = game_size;
							 | 
						||
| 
								 | 
							
												this.cell_count = game_size * game_size;
							 | 
						||
| 
								 | 
							
												for (let row = 0; row < this.game_size; row++) {
							 | 
						||
| 
								 | 
							
													for (let col = 0; col < this.game_size; col++) {
							 | 
						||
| 
								 | 
							
														this.cells.push(new Cell(this, row, col));
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												console.info("Loading session");
							 | 
						||
| 
								 | 
							
												setTimeout(()=>{
							 | 
						||
| 
								 | 
							
													if(_this.status == "applying state"){
							 | 
						||
| 
								 | 
							
														_this.deleteSession();
							 | 
						||
| 
								 | 
							
														window.location.reload();
							 | 
						||
| 
								 | 
							
													}else{
							 | 
						||
| 
								 | 
							
														console.info("Finished session validation");
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												},10000);
							 | 
						||
| 
								 | 
							
												this.loadSession();
							 | 
						||
| 
								 | 
							
												document.addEventListener('keypress', (e) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													if (!isNaN(e.key) || e.key == 'd') {
							 | 
						||
| 
								 | 
							
														let number = e.key == 'd' ? 0 : Number(e.key);
							 | 
						||
| 
								 | 
							
														_this.addNumberToSelection(number);
							 | 
						||
| 
								 | 
							
														//console.info({set:Number(e.key)});
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													if (e.key == 'p') {
							 | 
						||
| 
								 | 
							
														_this.toggleParser();
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													if (e.key == 'u') {
							 | 
						||
| 
								 | 
							
														_this.popState();
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													if (e.key == 'r') {
							 | 
						||
| 
								 | 
							
														if (this.selection().length) {
							 | 
						||
| 
								 | 
							
															this.pushState();
							 | 
						||
| 
								 | 
							
															_this.deSelectAll();
							 | 
						||
| 
								 | 
							
														} else {
							 | 
						||
| 
								 | 
							
															let state = this.getLastState();
							 | 
						||
| 
								 | 
							
															if (state) {
							 | 
						||
| 
								 | 
							
																state.cells.filter(cell => cell.selected).forEach(cell => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
																	this.getCellByName(cell.name).select();
							 | 
						||
| 
								 | 
							
																})
							 | 
						||
| 
								 | 
							
															}
							 | 
						||
| 
								 | 
							
														}
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('mousemove', (e) => {
							 | 
						||
| 
								 | 
							
													//this.pushState();
							 | 
						||
| 
								 | 
							
												})
							 | 
						||
| 
								 | 
							
												document.addEventListener('dblclick', (e) => {
							 | 
						||
| 
								 | 
							
													_this.previousSelection = _this.selection();
							 | 
						||
| 
								 | 
							
													_this.cells.forEach(cell => {
							 | 
						||
| 
								 | 
							
														cell.deSelect();
							 | 
						||
| 
								 | 
							
													});
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												document.addEventListener('contextmenu', (e) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('mouseexit', (e) => {
							 | 
						||
| 
								 | 
							
													// Edge case while holding mouse button while dragging out. Should save state
							 | 
						||
| 
								 | 
							
													_this.pushState();
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.element.addEventListener('mouseup', (e) => {
							 | 
						||
| 
								 | 
							
													_this.pushState();
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												this.pushState()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											isValid() {
							 | 
						||
| 
								 | 
							
												return this.cells.filter(cell => !cell.isValid()).length == 0
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											createState() {
							 | 
						||
| 
								 | 
							
												const state = new State(this.state_number)
							 | 
						||
| 
								 | 
							
												let selectedCount = 0;
							 | 
						||
| 
								 | 
							
												state.cells = this.cells.map(cell => {
							 | 
						||
| 
								 | 
							
													if (cell.selected) {
							 | 
						||
| 
								 | 
							
														selectedCount++;
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													return { name: cell.name, values: cell.values, value: cell.value, selected: cell.selected, state: cell.getState() }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
												state.selectedCount = selectedCount;
							 | 
						||
| 
								 | 
							
												return state;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											pushState() {
							 | 
						||
| 
								 | 
							
												const state = this.createState();
							 | 
						||
| 
								 | 
							
												const previousState = this.getLastState();
							 | 
						||
| 
								 | 
							
												if (!previousState || !previousState.equals(state)) {
							 | 
						||
| 
								 | 
							
													this.states.push(state);
							 | 
						||
| 
								 | 
							
													this.state_number++;
							 | 
						||
| 
								 | 
							
													this.saveSession();
							 | 
						||
| 
								 | 
							
													//console.info({ pushState: state.getHash(), length: state.getHash().length, number: state.number });
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											refreshState() {
							 | 
						||
| 
								 | 
							
												const state = this.getLastState();
							 | 
						||
| 
								 | 
							
												if (!state)
							 | 
						||
| 
								 | 
							
													return null;
							 | 
						||
| 
								 | 
							
												this.applyState(state)
							 | 
						||
| 
								 | 
							
												return state;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											getLastState() {
							 | 
						||
| 
								 | 
							
												return this.states.length ? this.states.at(this.states.length - 1) : null;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											applyState(state) {
							 | 
						||
| 
								 | 
							
												this.status = "applying state";
							 | 
						||
| 
								 | 
							
												state.cells.forEach(stateCell => {
							 | 
						||
| 
								 | 
							
													const cell = this.getCellByName(stateCell.name);
							 | 
						||
| 
								 | 
							
													cell.selected = stateCell.selected;
							 | 
						||
| 
								 | 
							
													cell.values = stateCell.values;
							 | 
						||
| 
								 | 
							
													cell.value = stateCell.value;
							 | 
						||
| 
								 | 
							
													cell.update();
							 | 
						||
| 
								 | 
							
												})
							 | 
						||
| 
								 | 
							
												this.status = "applied state"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											popState() {
							 | 
						||
| 
								 | 
							
												let state = this.states.pop();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if (!state)
							 | 
						||
| 
								 | 
							
													return;
							 | 
						||
| 
								 | 
							
												if (state.equals(this.createState())) {
							 | 
						||
| 
								 | 
							
													return this.popState();
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.applyState(state);
							 | 
						||
| 
								 | 
							
												this.saveSession();
							 | 
						||
| 
								 | 
							
												//console.info({ popState: state.getHash(), length: state.getHash().length, number: state.number });
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											getCellByName(name) {
							 | 
						||
| 
								 | 
							
												return this.cells.filter(cell => {
							 | 
						||
| 
								 | 
							
													return cell.name == name
							 | 
						||
| 
								 | 
							
												})[0]
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											deSelectAll() {
							 | 
						||
| 
								 | 
							
												this.cells.forEach(cell => {
							 | 
						||
| 
								 | 
							
													cell.deSelect();
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											selection() {
							 | 
						||
| 
								 | 
							
												return this.cells.filter(cell => {
							 | 
						||
| 
								 | 
							
													return cell.selected
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											onSelectionToggle() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											addNumberToSelection(number) {
							 | 
						||
| 
								 | 
							
												const _this = this;
							 | 
						||
| 
								 | 
							
												this.pushState();
							 | 
						||
| 
								 | 
							
												this.selection().forEach((cell) => {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
													cell.addNumber(number)
							 | 
						||
| 
								 | 
							
													cell.update();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												})
							 | 
						||
| 
								 | 
							
												_this.pushState();
							 | 
						||
| 
								 | 
							
												if (this.isValid()) {
							 | 
						||
| 
								 | 
							
													this.deSelectAll();
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												_this.pushState();
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											onCellDblClick(cell) {
							 | 
						||
| 
								 | 
							
												this.previousSelection = this.selection();
							 | 
						||
| 
								 | 
							
												this.popState()
							 | 
						||
| 
								 | 
							
												let originalSelected = this.selctedCells
							 | 
						||
| 
								 | 
							
												if (cell.selected) {
							 | 
						||
| 
								 | 
							
													this.selectedCells.push(cell);
							 | 
						||
| 
								 | 
							
												} else {
							 | 
						||
| 
								 | 
							
													this.selectedCells.pop(cell);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												if (!this.originalSelected != this.selectedCells) {
							 | 
						||
| 
								 | 
							
													this.popState();
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												//console.info({selected:this.selectedCells});
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											render() {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												this.cells.forEach(cell => {
							 | 
						||
| 
								 | 
							
													cell.render(this.element);
							 | 
						||
| 
								 | 
							
												});
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										const sudoku = new Sudoku(container, 9);
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										sudoku.render();
							 | 
						||
| 
								 | 
							
										const app = sudoku;
							 | 
						||
| 
								 | 
							
										/*
							 | 
						||
| 
								 | 
							
										document.addEventListener('contextmenu',(e)=>{
							 | 
						||
| 
								 | 
							
											e.preventDefault();
							 | 
						||
| 
								 | 
							
										});*/
							 | 
						||
| 
								 | 
							
										/*
							 | 
						||
| 
								 | 
							
										for(let i = 0; i < game_size*game_size; i++){
							 | 
						||
| 
								 | 
							
											const cell = document.importNode(cell_template.content,true);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											app.appendChild(cell);
							 | 
						||
| 
								 | 
							
										}*/
							 | 
						||
| 
								 | 
							
										//document.body.appendChild(app);
							 | 
						||
| 
								 | 
							
									</script>
							 | 
						||
| 
								 | 
							
								</body>
							 |