|  | <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> |