PHP coder here. Learning JavaScript this week. I made a sudoku game for practice. Features:
- Import puzzles by pasting a text string.
- Imported squares are gray. Your squares are white.
- Program doesn't let you make a clearly illegal move. It will blank your number and color the square red for 2 seconds.
- The sudoku puzzle/game is a class. Easy to add methods and features to it later.
- Room for growth. I may add a "solve" button and a "give hints" check box later. I may add a "you won" message when you win.
Things I'm hoping to gain from this Code Review.
- Let's focus on the JavaScript. Improvements to the JavaScript code's organization, style, choice of functions, variable and function names, etc.
- Feel free to refactor completely if you want.
- Looking for that professional coder perspective so I can build good habits.
- Feel free to suggest next steps/next features.
Fiddle - https://jsfiddle.net/AdmiralAkbar2/z6rv70h4/37/
Screenshot
JavaScript
// TODO: solve button
// TODO: show hints
// if 8 squares in a 3x3 are known, highlight 9th square
// if 8 squares in a row are known, highlight 9th square
// if 8 squares in a column are known, highlight 9th square
"use strict";
class Sudoku {
constructor() {
this.board = this.blank_board_array();
}
blank_board_array() {
return [
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]
];
}
// I can't figure out how to get this working with the "set" keyword, so making a method for now
set_board(board_string) {
if ( ! board_string.match(/^\d{81}$/m) ) {
this.board = this.blank_board_array();
return;
}
for ( let row = 0; row <= 8; row++ ) {
for ( let column = 0; column <= 8; column++ ) {
this.board[row][column] = board_string.charAt(row*9+column);
}
}
/*
if ( ! this.puzzle_is_valid() ) {
this.board = this.blank_board_array();
return;
}
*/
}
get_board_array() {
return this.board;
}
make_move(row, col, value) {
this.board[row][col] = value;
}
is_legal_move(row, col, value) {
// check for non numbers
// weird that JS match function doesn't put quotes around regex
if ( ! value.match(/^[1-9]$/m) ) {
return false;
}
// check row
for ( let i = 0; i <= 8; i++ ) {
if ( value == this.board[row][i] ) {
return false;
}
}
// check column
for ( let i = 0; i <= 8; i++ ) {
if ( value == this.board[i][col] ) {
return false;
}
}
// check 3x3 grid
let row_offset = Math.floor(row/3)*3;
let col_offset = Math.floor(col/3)*3;
for ( let i = 0 + row_offset; i <= 2 + row_offset; i++ ) {
for ( let j = 0 + col_offset; j <= 2 + col_offset; j++ ) {
if ( value == this.board[i][j] ) {
return false;
}
}
}
return true;
}
};
let game1 = new Sudoku();
let import_string;
let import_button = document.getElementById('import');
let sudoku_squares = createArray(9,9);
for ( let row = 0; row <= 8; row++ ) {
for ( let col = 0; col <= 8; col++ ) {
sudoku_squares[row][col] = document.getElementsByClassName('sudoku')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[row].getElementsByTagName('td')[col].getElementsByTagName('input')[0];
}
}
import_button.addEventListener("mouseup", function() {
import_string = document.getElementsByName("import_string")[0].value;
game1.set_board(import_string);
print_sudoku_to_webpage(game1);
});
for ( let row = 0; row <= 8; row++ ) {
for ( let col = 0; col <= 8; col++ ) {
sudoku_squares[row][col].addEventListener('input', function(e) {
e.target.classList.remove("invalid");
if ( ! game1.is_legal_move(row, col, e.target.value) && e.target.value != "" ) {
e.target.value = "";
highlight_temporarily(e.target, 2000);
} else {
game1.make_move(row, col, e.target.value);
}
});
}
}
function print_sudoku_to_webpage(sudoku_object) {
let board = sudoku_object.get_board_array();
clear_webpage_board();
for ( let row = 0; row <= 8; row++ ) {
for ( let col = 0; col <= 8; col++ ) {
if ( board[row][col] != 0 ) {
let input = sudoku_squares[row][col];
input.value = board[row][col];
input.classList.add('imported_square');
}
}
}
}
function clear_webpage_board() {
for ( let row = 0; row <= 8; row++ ) {
for ( let col = 0; col <= 8; col++ ) {
sudoku_squares[row][col].value = "";
sudoku_squares[row][col].classList.remove('imported_square');
}
}
}
// This code is borrowed from another website. Thanks google.
function createArray(length) {
var arr = new Array(length || 0),
i = length;
if (arguments.length > 1) {
var args = Array.prototype.slice.call(arguments, 1);
while(i--) arr[length-1 - i] = createArray.apply(this, args);
}
return arr;
}
function highlight_temporarily(obj, timeout_in_ms){
obj.classList.add('invalid');
setTimeout(function(){
obj.classList.remove('invalid');
}, timeout_in_ms);
}
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sudoku</title>
</head>
<body>
<div class="import">
<input type="text" name="import_string" value="080100007000070960026900130000290304960000082502047000013009840097020000600003070" />
<br />
<button id="import">Import</button>
<!--
<button id="solve">Solve</button>
<input type="checkbox" value="1" name="hints" /> Show Hints
-->
</div>
<table class="sudoku">
<tbody>
<tr class="1">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
<tr class="2">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
<tr class="3 thick_bottom">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
<tr class="4">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
<tr class="5">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
<tr class="6 thick_bottom">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
<tr class="7">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
<tr class="8">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
<tr class="9">
<td class="1"><input type="text" maxlength="1" /></td>
<td class="2"><input type="text" maxlength="1" /></td>
<td class="3 thick_right"><input type="text" maxlength="1" /></td>
<td class="4"><input type="text" maxlength="1" /></td>
<td class="5"><input type="text" maxlength="1" /></td>
<td class="6 thick_right"><input type="text" maxlength="1" /></td>
<td class="7"><input type="text" maxlength="1" /></td>
<td class="8"><input type="text" maxlength="1" /></td>
<td class="9"><input type="text" maxlength="1" /></td>
</tr>
</tbody>
</table>
</body>
</html>
CSS
body {font-family:sans-serif;}
.import {padding-bottom:0.2em;}
.import input[type=text] {width:630px;}
.valid {background-color:limegreen;}
.invalid {background-color:red;}
.imported_square {background-color:lightgray;}
.sudoku {border:4px solid black; border-collapse: collapse;}
.sudoku tr {padding:0;}
.sudoku td {padding:0; border:1px solid black; width:1em;}
.sudoku input {width:1em; border:0; font-size:25pt; text-align:center;}
.sudoku .thick_right {border-right:4px solid black !important;}
.sudoku .thick_bottom {border-bottom:4px solid black;}