Project
I've created a puzzle site with automatically generated sudokus.
For more information on Sudokus, please see the wikipedia article.
The code
const GRID_SIZE = 9;
const REMOVE = 50;
let solution = createArray(9, 9);
let textIsVisible = false;
window.addEventListener('load', function() {
initialize();
});
document.getElementById('submit').addEventListener('click', function() {
let elements = document.getElementsByTagName('input');
let k = 0;
for(let i = 0; i < 9; i++) {
for(let j = 0; j < 9; j++) {
let value = parseInt(elements[k].value);
if(value !== solution[i][j]) {
textIsVisible = true;
$('#text').text('That\'s not the correct solution!');
return;
}
k++;
}
}
textIsVisible = true;
$('#text').text('Correct!');
});
document.getElementById('reset').addEventListener('click', function() {
initialize();
});
function initialize() {
textIsVisible = false;
$('#text').text('');
let elements = document.getElementsByTagName('input');
for(let i = 0; i < elements.length; i++) {
elements[i].classList.add('focus');
elements[i].removeAttribute('readonly');
elements[i].classList.remove('bold');
elements[i].addEventListener('click', function() {
$('#text').text('');
});
}
let sudoku = generate();
solveSudoku(sudoku, 0);
showSudoku(elements, sudoku);
}
function generate() {
//Fill with zeros
let sudoku = createArray(9, 9);
for(let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
sudoku[i][j] = 0;
}
}
//Add ten random numbers
for(let i = 0; i < 10; i++) {
sudoku = addNumber(sudoku);
}
/*
* Add random numbers in the first row in addition to random numbers
* in addition to random numbers on random positions
*/
sudoku[0][1] = legalNumbers(sudoku, 0, 1);
sudoku[0][4] = legalNumbers(sudoku, 0, 4);
sudoku[0][7] = legalNumbers(sudoku, 0, 7);
//Solve sudoku
solveSudoku(sudoku, 0);
for(let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
sudoku[i][j] = solution[i][j];
}
}
/*
* Remove elements so that sudoku still has unique solution
*/
let i = 0;
while(i < REMOVE) {
let row = Math.floor(Math.random() * 9);
let col = Math.floor(Math.random() * 9);
if(sudoku[row][col] !== 0) {
let cache = sudoku[row][col];
sudoku[row][col] = 0;
if(solveSudoku(sudoku, 0) !== 1) {
/*
* Go one step back if sudoku doesn't have unique solution
* anymore
*/
sudoku[row][col] = cache;
}
else {
i++;
}
}
}
return sudoku;
}
function createArray(rows, cols) {
const array = new Array(rows);
for (let i = 0; i < cols; i++) {
array[i] = new Array(cols);
}
return array;
}
function addNumber(sudoku) {
/*
* Find random position to add number
*/
let row = Math.floor(Math.random() * 9);
let col = Math.floor(Math.random() * 9);
/*
* Add random, but legal number
*/
sudoku[row][col] = legalNumbers(sudoku, row, col);
return sudoku;
}
function solveSudoku(sudoku, count) {
for(let i = 0; i < GRID_SIZE; i++) {
for (let j = 0; j < GRID_SIZE; j++) {
/*
* Only empty fields will be changd
*/
if(sudoku[i][j] === 0) {
/*
* Try all numbers between 1 and 9
*/
for(let n = 1; n <= GRID_SIZE && count < 2; n++) {
/*
* Is number n safe?
*/
if(checkRow(sudoku, i, n) && checkColumn(sudoku, j, n) && checkBox(sudoku, i, j, n)) {
sudoku[i][j] = n;
let cache = solveSudoku(sudoku, count);
/*
* new solution found
*/
if(cache > count) {
count = cache;
/*
* Save new solution
*/
for(let k = 0; k < GRID_SIZE; k++) {
for(let l = 0; l < GRID_SIZE; l++) {
if(sudoku[k][l] !== 0) {
solution[k][l] = sudoku[k][l];
}
}
}
sudoku[i][j] = 0;
}
/*
* Not a solution, go one step back
*/
else {
sudoku[i][j] = 0;
}
}
}
/*
* No other solution found
*/
return count;
}
}
}
/*
* found another solution
*/
return count + 1;
}
function showSudoku(elements, sudoku) {
let k = 0;
for(let i = 0; i < GRID_SIZE; i++) {
for(let j = 0; j < GRID_SIZE; j++) {
if(sudoku[i][j] > 0) {
elements[k].value = sudoku[i][j];
elements[k].setAttribute('readonly', 'true');
elements[k].classList.remove('focus');
elements[k].classList.add('bold');
}
else {
elements[k].value = '';
}
k++;
}
}
}
/*
* Helper functions
*/
function checkRow(sudoku, row, n) {
for(let i = 0; i < GRID_SIZE; i++) {
if(sudoku[row][i] === n) {
return false;
}
}
return true;
}
function checkColumn(sudoku, col, n) {
for(let i = 0; i < GRID_SIZE; i++) {
if(sudoku[i][col] === n) {
return false;
}
}
return true;
}
function checkBox(sudoku, row, col, n) {
row = row - row % 3;
col = col - col % 3;
for(let i = row; i < row + 3; i++) {
for(let j = col; j < col + 3; j++) {
if(sudoku[i][j] === n) {
return false;
}
}
}
return true;
}
function legalNumbers(sudoku, row, col) {
let array = [];
for(let i = 1; i <= 9; i++) {
if(checkRow(sudoku, row, i) && checkColumn(sudoku, col, i) && checkBox(sudoku, row, col, i)) {
array.push(i);
break;
}
}
return array[Math.floor(Math.random() * array.length)];
}
function isNumber(elem, e) {
if(elem.value.length !== 0) {
return false;
}
let id = e.key;
let string = '123456789';
for(let i = 0; i < string.length; i++) {
if(string.charAt(i) === id) {
return true;
}
}
return false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' type='text/css' href='fonts/baloo/stylesheet.css'>
<link rel='stylesheet' type='text/css' href='fonts/open-sans/stylesheet.css'>
<link rel='shortcut icon' href='logo.svg'/>
<script src='jquery.js'></script>
<title>Sudoku</title>
<style>
/*
* Font sizes
*/
html {
font-size: 18px;
}
p {
font-size: 1em;
}
h1 {
font-size: 1.8em;
}
h3 {
font-size: 1.42em;
}
/*
* 'Fork me on GitHub'-image
*/
#fork {
position: absolute;
width: 160px;
height: 160px;
top: 0;
border: 0;
right: 0;
}
/*
* Main style
*/
body {
font-family: 'open-sans',sans-serif;
font-weight: normal;
line-height: 1.65;
margin-top: 30px;
}
.inner {
width: 350px;
margin: 0 auto;
}
/*
* Sudoku field container
*/
.sudoku {
line-height: 34px;
text-align: center;
}
.mainContainer {
display: inline-block;
}
.container {
line-height: 36px;
}
h1 {
font-family: 'balooregular', serif;
font-weight: normal;
color: #00410B;
}
/*
* Input fields main style
*/
input {
border: none;
text-align: center;
font-size: 1em;
width: 30px;
height: 30px;
float: left;
border-right: 2px solid black;
border-bottom: 2px solid black;
}
/*
* Don't show arrow buttons inside input fields
*/
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
display: none;
}
input[type=number] {
-moz-appearance:textfield;
}
input:focus, button:focus {
outline: none;
}
/*
* Input field additional border styles
*/
.left {
border-left: 2px solid black;
}
.right {
border-right: 2px solid red;
}
.top {
border-top: 2px solid black;
}
.bottom {
border-bottom: 2px solid red;
}
/*
* Given numbers will be bold
*/
.bold {
font-weight: bold;
font-size: 1em;
}
/*
* Fields that user has to fill out
* will be LightGray on focus
*/
.focus:focus {
background: LightGray;
}
/*
* Style of buttons
*/
button {
border: 1px solid black;
border-radius: 5px;
padding: 10px;
width: 40%;
text-align: center;
transition: 0.3s ease;
margin: 20px 5% 30px;
float: left;
}
button:hover {
color: #FFFFF0;
background: #00410B;
}
/*
* links
*/
a {
color: #2B823A;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/*
* Responsive
*/
@media (max-width: 650px) {
#fork {
display: none;
}
}
@media (max-width: 350px) {
html {
font-size: 16px;
}
.inner {
width: 100%;
}
}
@media (max-width: 330px) {
.sudoku {
line-height: 24px;
text-align: center;
}
.container {
line-height: 26px;
}
input {
border: none;
text-align: center;
font-size: 1em;
width: 20px;
height: 20px;
float: left;
border-right: 2px solid black;
border-bottom: 2px solid black;
}
}
</style>
</head>
<body>
<a href='https://github.com/philippwilhelm/Sudoku' id='fork'><img alt='' src='fork.png'></a>
<div class='inner'>
<h1>Sudoku</h1>
<div class='sudoku'>
<div class='mainContainer'>
<div class='container'>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='left top'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='top'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right top'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft top'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='top'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right top'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft top'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='top'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='top'/><br>
</div>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='left'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><br>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom left'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom'/><br>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='left'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><br>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='left'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><br>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom left'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='bottom'/><br>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='left'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><br>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='left'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><br>
<input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='left'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='right'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class='notLeft'/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><input maxlength='1' onkeypress='return isNumber(this, event)' type='number' min='0' max='9' value='0' class=''/><br>
</div>
</div>
<button id='submit'>Submit</button><button id='reset'>New Game</button>
<span id='text'></span>
<script src='sudoku_generator.js'></script>
</div>
</body>
</html>
Question
All suggestions are welcome. The html-file only is a minimal working example, so it is not really worth reviewing.