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