我用自动生成的sudokus创建了一个益智网站。
有关Sudokus的更多信息,请参见维基百科文章。
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;
} Sudoku
/*
* 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;
}
}
Sudoku
SubmitNew Game欢迎所有建议。html文件只是一个极小的工作示例,因此它实际上不值得审查。
发布于 2020-11-23 17:50:59
Always使用 const when (可能)--仅在需要重新分配变量名的想要警告其他读者使用代码时才使用let。如果不重新分配某些内容,请向读者表明,他们不必担心使用const而不是let。
一个函数的Anonymous回调包装器是多余的,至少在几乎所有情况下都是如此--只需将单个函数作为回调传递即可(参见下面)。
Prefer DOMContentLoaded over load - load等待所有资源完全完成下载,包括可能的重量级图像。最好是在DOM被填充后立即使页面具有交互性,而不是等待子资源完成。
window.addEventListener('DOMContentLoaded', initialize);或者,由于您使用的是jQuery,您也可以这样做:
$(initialize);jQuery还是普通DOM方法?目前,您只为一个目的使用jQuery --调用.text,设置元素的文本。这很奇怪: jQuery是一个很大的库,可以简单地使用textContent来设置元素文本,而不需要库。如果您确实想将它变成一个jQuery项目(我不建议这样做,因为它目前似乎还没有完成任何有用的事情),那么无论何时使用DOM操作,使用jQuery在风格上都会更加一致。
textIsVisible 不执行任何引用4次,但总是分配给它,永远不要从中读取-最好完全删除它。
Prefer数组方法和迭代器在需要手动迭代且没有抽象的for循环上。例如,考虑到:
let elements = document.getElementsByTagName('input');
for(let i = 0; i < elements.length; i++) {
elements[i].classList.add('focus');
// do lots of other stuff with elements[i]除非您需要在其他地方使用索引,而不是引用elements[i],否则用迭代器编写干的和更抽象的代码就更有意义了。它也更短:
for (const element of elements) {
element.classList.add('focus');
// do lots of other stuff with elementAleksei的回答也展示了如何在代码的其他部分中使用数组方法。
Condense类名,因为单元格总是有两个类中的一个,即focus或bold,可以考虑只使用一个类来切换--也许是focus。例如,而不是:
.bold {
font-weight: bold;
font-size: 1em;
}使用
.container > input {
font-weight: bold;
font-size: 1em;
}其规则被下面的.container > .focus规则覆盖。
不要将一个变量重新分配到您已经有一个引用的同一个对象。这就是:
sudoku = addNumber(sudoku);应该是
addNumber(sudoku);Use注释来描述为什么,或者什么如果 what不是明显的-例如:
// Go one step back if sudoku doesn't have unique solution anymore是好的,但是
//Solve sudoku
solveSudoku(sudoku, 0);并不能传达任何有用的信息。
solveSudoku有些混乱:它返回一个调用者经常不使用的值,并将解决方案重新分配到一个与调用方完全断开连接的变量中。例如:
let sudoku = generate();
solveSudoku(sudoku, 0);
showSudoku(elements, sudoku);solveSudoku的目的是神秘的,直到你阅读这个函数来找出它的副作用是什么。函数是最容易理解的一目了然,当它们是纯粹的,或至少不是太不纯。在这里,考虑从solveSudoku返回值并使用它,例如:
const sudoku = generate();
solution = solveSudoku(sudoku, 0).solution; // with `numUniqueSolutions` as the other property in the object
showSudoku(elements, sudoku);乍一看,这更有意义。您可以更进一步,完全避免重新分配solution变量,但是这需要一些重要的重构。
上述方法还使solveSudoku返回的值类型更加清晰:numUniqueSolutions (从当前的solveSudoku实现看,这一点并不明显)。
发布于 2020-11-23 15:57:57
我建议用函数式编写代码,而不是过多地使用循环。这将使您的代码更加简洁(您可以将解决方案的代码减少一半)和可读性。我重写了你的几个函数,让你明白我的意思:
const range = (from, to) => [...Array(to - from + 1).keys()].map((i) => i + from);
const checkRow = (sudoku, row, n) => range(0, GRID_SIZE - 1).every((x) => sudoku[row][x] !== n);
const checkColumn = (sudoku, col, n) => range(0, GRID_SIZE - 1).every((x) => sudoku[x][col] !== n);
const combine = (arr1, arr2) => arr1.reduce((result, x) => result.concat(arr2.map((y) => [x, y])), []);
const boxCells = (row, col) => {
row = row - row % 3;
col = col - col % 3;
return combine(range(row, row + 2), range(col, col + 2));
}
const checkBox = (sudoku, row, col, n) => boxCells(row, col).every(([r, c]) => sudoku[r][c] !== n);
const legalNumbers = (sudoku, row, col) => {
const valid = (x) => checkRow(sudoku, row, x) && checkColumn(sudoku, col, x) && checkBox(sudoku, row, col, x);
return range(1, 9).find(valid);
}
const isNumber = (elem, { key }) => elem.value === '' && '123456789'.split('').includes(key);https://codereview.stackexchange.com/questions/252518
复制相似问题