<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>SUDOKU</title>
<style>
H1 {
background: black;
color: white;
margin: 0px 0px 2px 0px;
font-size: 20px;
padding: 2px 1px 5px 1px;
text-align: center;
}
body {
font-family: verdana, helvetica, arial, sans-serif;
border: 0px;
margin: 0px;
padding: 0px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAIUlEQVQYV2N89urtfwYiACNIoZSYMCMhtaMK8YYQ0cEDAG5yJ8eLRhTfAAAAAElFTkSuQmCC)
repeat;
}
.sudoku_board {
margin: 6px auto;
overflow: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
box-shadow: 0px 0px 5px 5px #bdc3c7;
}
.sudoku_board .cell {
width: 11.11%;
display: inline-block;
float: left;
cursor: pointer;
text-align: center;
overflow: hidden;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
box-shadow: 0px 0px 0px 1px #bdc3c7;
background: white;
}
.sudoku_board .cell.border_h {
box-shadow: 0px 0px 0px 1px #bdc3c7, inset 0px -2px 0 0 #34495e;
}
.sudoku_board .cell.border_v {
box-shadow: 0px 0px 0px 1px #bdc3c7, inset -2px 0 0 #34495e;
}
.sudoku_board .cell.border_h.border_v {
box-shadow: 0px 0px 0px 1px #bdc3c7, inset -2px 0 0 black,
inset 0px -2px 0 black;
}
.sudoku_board .cell span {
color: #2c3e50;
font-size: 14px;
text-align: middle;
}
.sudoku_board .cell.selected,
.sudoku_board .cell.selected.fix {
background: #ffe;
}
.sudoku_board .cell.selected.current {
position: relative;
background: #3498db;
font-weight: bold;
box-shadow: 0px 0px 3px 3px #bdc3c7;
}
.sudoku_board .cell.selected.current span {
color: white;
}
.sudoku_board .cell.selected.group {
color: blue;
}
.sudoku_board .cell span.samevalue,
.sudoku_board .cell.fix span.samevalue {
font-weight: bold;
color: #3498db;
}
.sudoku_board .cell.notvalid,
.sudoku_board .cell.selected.notvalid {
font-weight: bold;
color: white;
background: #e74c3c;
}
.sudoku_board .cell.fix {
background: #ecf0f1;
cursor: not-allowed;
}
.sudoku_board .cell.fix span {
color: #7f8c8d;
}
.sudoku_board .cell .solution {
font-size: 10px;
color: #d35400;
}
.sudoku_board .cell .note {
color: #bdc3c7;
width: 50%;
height: 50%;
display: inline-block;
float: left;
text-align: center;
font-size: 14px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.gameover_container .gameover {
color: white;
font-weight: bold;
text-align: center;
display: block;
position: absolute;
width: 90%;
padding: 10px;
box-shadow: 0px 0px 5px 5px #bdc3c7;
}
.restart {
background: #7f8c8d;
color: #ecf0f1;
}
.board_console_container,
.gameover_container {
background-color: rgba(127, 140, 141, 0.7);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.board_console {
display: block;
position: absolute;
width: 50%;
color: white;
background-color: rgba(127, 140, 141, 0.7);
box-shadow: 0px 0px 5px 5px #bdc3c7;
}
.board_console .num {
width: 33.33%;
color: #2c3e50;
padding: 1px;
display: inline-block;
font-weight: bold;
font-size: 24px;
text-align: center;
cursor: pointer;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
box-shadow: 0px 0px 0px 1px #bdc3c7;
}
.board_console .num:hover {
color: white;
background: #f1c40f;
}
.board_console .num.remove {
width: 66.66%;
}
.board_console .num.note {
background: #95a5a6;
color: #ecf0f1;
}
.board_console .num.note:hover {
background: #95a5a6;
color: #f1c40f;
}
.board_console .num.selected {
background: #f1c40f;
box-shadow: 0px 0px 3px 3px #bdc3c7;
}
.board_console .num.note.selected {
background: #f1c40f;
box-shadow: 0px 0px 3px 3px #bdc3c7;
}
.board_console .num.note.selected:hover {
color: white;
}
.board_console .num.no:hover {
color: white;
cursor: not-allowed;
}
.board_console .num.remove:hover {
color: white;
background: #c0392b;
}
.statistics {
text-align: center;
}
#sudoku_menu {
background-color: black;
position: absolute;
z-index: 2;
width: 100%;
height: 100%;
left: -100%;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
#sudoku_menu ul {
margin: 0;
padding: 100px 0px 0px 0px;
list-style: none;
}
#sudoku_menu ul li {
margin: 0px 50px;
}
#sudoku_menu ul li a {
text-align: center;
padding: 15px 20px;
font-size: 28px;
font-weight: bold;
color: white;
text-decoration: none;
display: block;
border-bottom: 1px solid #2c3e50;
}
#sudoku_menu.open-sidebar {
left: 0px;
}
#sidebar-toggle {
z-index: 3;
background: #bdc3c7;
border-radius: 3px;
display: block;
position: relative;
padding: 22px 18px;
float: left;
}
#sidebar-toggle .bar {
display: block;
width: 28px;
margin-bottom: 4px;
height: 4px;
background-color: #f0f0f0;
border-radius: 1px;
}
#sidebar-toggle .bar:last-child {
margin-bottom: 0;
}
@media all and (orientation: portrait) and (min-width: 640px) {
h1 {
font-size: 50px;
}
.statistics {
font-size: 30px;
}
.sudoku_board .cell span {
font-size: 60px;
}
.board_console .num {
font-size: 60px;
}
}
@media all and (orientation: landscape) and (min-height: 640px) {
h1 {
font-size: 50px;
}
.statistics {
font-size: 30px;
}
.sudoku_board .cell span {
font-size: 50px;
}
.board_console .num {
font-size: 50px;
}
}
@media all and (orientation: portrait) and (max-width: 1000px) {
.sudoku_board .cell span {
font-size: 30px;
}
}
@media all and (orientation: portrait) and (max-width: 640px) {
.sudoku_board .cell span {
font-size: 24px;
}
.sudoku_board .cell .note {
font-size: 10px;
}
}
@media all and (orientation: portrait) and (max-width: 470px) {
.sudoku_board .cell span {
font-size: 16px;
}
.sudoku_board .cell .note {
font-size: 8px;
}
}
@media all and (orientation: portrait) and (max-width: 320px) {
.sudoku_board .cell span {
font-size: 12px;
}
.sudoku_board .cell .note {
font-size: 8px;
}
}
@media all and (orientation: portrait) and (max-width: 240px) {
.sudoku_board .cell span {
font-size: 10px;
}
}
</style>
</head>
<body>
<a id="sidebar-toggle">
<span class="bar"></span>
<span class="bar"></span>
<span class="bar"></span>
</a>
<h1 id="sudoku_title">数独</h1>
<div id="sudoku_menu">
<ul>
<li><a class="restart">新游戏</a></li>
<li></li>
</ul>
</div>
<div id="sudoku_container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script>
function Sudoku(params) {
var t = this;
this.INIT = 0;
this.RUNNING = 1;
this.END = 2;
this.id = params.id || 'sudoku_container';
this.displaySolution = params.displaySolution || 0;
this.displaySolutionOnly = params.displaySolutionOnly || 0;
this.displayTitle = params.displayTitle || 0;
this.highlight = params.highlight || 0;
this.fixCellsNr = params.fixCellsNr || 32;
this.n = 3;
this.nn = this.n * this.n;
this.cellsNr = this.nn * this.nn;
if (this.fixCellsNr < 10) this.fixCellsNr = 10;
if (this.fixCellsNr > 70) this.fixCellsNr = 70;
this.init();
setInterval(function () {
t.timer();
}, 1000);
return this;
}
Sudoku.prototype.init = function () {
this.status = this.INIT;
this.cellsComplete = 0;
this.board = [];
this.boardSolution = [];
this.cell = null;
this.markNotes = 0;
this.secondsElapsed = 0;
if (this.displayTitle == 0) {
$('#sudoku_title').hide();
}
this.board = this.boardGenerator(this.n, this.fixCellsNr);
return this;
};
Sudoku.prototype.timer = function () {
if (this.status === this.RUNNING) {
this.secondsElapsed++;
$('.time').text('' + this.secondsElapsed);
}
};
Sudoku.prototype.shuffle = function (array) {
var currentIndex = array.length,
temporaryValue = 0,
randomIndex = 0;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
};
Sudoku.prototype.boardGenerator = function (n, fixCellsNr) {
var matrix_fields = [],
index = 0,
i = 0,
j = 0,
j_start = 0,
j_stop = 0;
this.boardSolution = [];
for (i = 0; i < this.nn; i++) {
matrix_fields[i] = i + 1;
}
matrix_fields = this.shuffle(matrix_fields);
for (i = 0; i < n * n; i++) {
for (j = 0; j < n * n; j++) {
var value = Math.floor(((i * n + i / n + j) % (n * n)) + 1);
this.boardSolution[index] = value;
index++;
}
}
var blank_indexes = [];
for (i = 0; i < this.n; i++) {
blank_indexes[i] = i + 1;
}
var bands_horizontal_indexes = this.shuffle(blank_indexes);
var board_solution_tmp = [];
index = 0;
for (i = 0; i < bands_horizontal_indexes.length; i++) {
j_start = (bands_horizontal_indexes[i] - 1) * this.n * this.nn;
j_stop = bands_horizontal_indexes[i] * this.n * this.nn;
for (j = j_start; j < j_stop; j++) {
board_solution_tmp[index] = this.boardSolution[j];
index++;
}
}
this.boardSolution = board_solution_tmp;
var bands_vertical_indexes = this.shuffle(blank_indexes);
board_solution_tmp = [];
index = 0;
for (k = 0; k < this.nn; k++) {
for (i = 0; i < this.n; i++) {
j_start = (bands_vertical_indexes[i] - 1) * this.n;
j_stop = bands_vertical_indexes[i] * this.n;
for (j = j_start; j < j_stop; j++) {
board_solution_tmp[index] = this.boardSolution[j + k * this.nn];
index++;
}
}
}
this.boardSolution = board_solution_tmp;
var board_indexes = [],
board_init = [];
for (i = 0; i < this.boardSolution.length; i++) {
board_indexes[i] = i;
board_init[i] = 0;
}
board_indexes = this.shuffle(board_indexes);
board_indexes = board_indexes.slice(0, this.fixCellsNr);
for (i = 0; i < board_indexes.length; i++) {
board_init[board_indexes[i]] = this.boardSolution[board_indexes[i]];
if (parseInt(board_init[board_indexes[i]]) > 0) {
this.cellsComplete++;
}
}
return this.displaySolutionOnly ? this.boardSolution : board_init;
};
Sudoku.prototype.drawBoard = function () {
var index = 0,
position = { x: 0, y: 0 },
group_position = { x: 0, y: 0 };
var sudoku_board = $('<div></div>').addClass('sudoku_board');
var sudoku_statistics = $('<div></div>')
.addClass('statistics')
.html(
'<b>完成:</b> <span class="cells_complete">' +
this.cellsComplete +
'/' +
this.cellsNr +
'</span> <b>时间:</b> <span class="time">' +
this.secondsElapsed +
'</span>'
);
$('#' + this.id).empty();
for (i = 0; i < this.nn; i++) {
for (j = 0; j < this.nn; j++) {
position = { x: i + 1, y: j + 1 };
group_position = {
x: Math.floor((position.x - 1) / this.n),
y: Math.floor((position.y - 1) / this.n),
};
var value = this.board[index] > 0 ? this.board[index] : '',
value_solution =
this.boardSolution[index] > 0 ? this.boardSolution[index] : '',
cell = $('<div></div>')
.addClass('cell')
.attr('x', position.x)
.attr('y', position.y)
.attr('gr', group_position.x + '' + group_position.y)
.html('<span>' + value + '</span>');
if (this.displaySolution) {
$(
'<span class="solution">(' + value_solution + ')</span>'
).appendTo(cell);
}
if (value > 0) {
cell.addClass('fix');
}
if (position.x % this.n === 0 && position.x != this.nn) {
cell.addClass('border_h');
}
if (position.y % this.n === 0 && position.y != this.nn) {
cell.addClass('border_v');
}
cell.appendTo(sudoku_board);
index++;
}
}
sudoku_board.appendTo('#' + this.id);
var sudoku_console_cotainer = $('<div></div>').addClass(
'board_console_container'
);
var sudoku_console = $('<div></div>').addClass('board_console');
for (i = 1; i <= this.nn; i++) {
$('<div></div>').addClass('num').text(i).appendTo(sudoku_console);
}
$('<div></div>')
.addClass('num remove')
.text('X')
.appendTo(sudoku_console);
$('<div></div>')
.addClass('num note')
.text('?')
.appendTo(sudoku_console);
var sudoku_gameover = $(
'<div class="gameover_container"><div class="gameover">Congratulation! <button class="restart">Play Again</button></div></div>'
);
sudoku_console_cotainer.appendTo('#' + this.id).hide();
sudoku_console.appendTo(sudoku_console_cotainer);
sudoku_statistics.appendTo('#' + this.id);
sudoku_gameover.appendTo('#' + this.id).hide();
this.resizeWindow();
};
Sudoku.prototype.resizeWindow = function () {
console.time('resizeWindow');
var screen = { w: $(window).width(), h: $(window).height() };
var b_pos = $('#' + this.id + ' .sudoku_board').offset(),
b_dim = {
w: $('#' + this.id + ' .sudoku_board').width(),
h: $('#' + this.id + ' .sudoku_board').height(),
},
s_dim = {
w: $('#' + this.id + ' .statistics').width(),
h: $('#' + this.id + ' .statistics').height(),
};
var screen_wr = screen.w + s_dim.h + b_pos.top + 10;
if (screen_wr > screen.h) {
$('#' + this.id + ' .sudoku_board').css(
'width',
screen.h - b_pos.top - s_dim.h - 14
);
$('#' + this.id + ' .board_console').css('width', b_dim.h / 2);
} else {
$('#' + this.id + ' .sudoku_board').css('width', '98%');
$('#' + this.id + ' .board_console').css('width', '50%');
}
var cell_width = $(
'#' + this.id + ' .sudoku_board .cell:first'
).width(),
note_with = Math.floor(cell_width / 2) - 1;
$('#' + this.id + ' .sudoku_board .cell').height(cell_width);
$('#' + this.id + ' .sudoku_board .cell span').css(
'line-height',
cell_width + 'px'
);
$('#' + this.id + ' .sudoku_board .cell .note').css({
'line-height': note_with + 'px',
width: note_with,
height: note_with,
});
var console_cell_width = $(
'#' + this.id + ' .board_console .num:first'
).width();
$('#' + this.id + ' .board_console .num').css(
'height',
console_cell_width
);
$('#' + this.id + ' .board_console .num').css(
'line-height',
console_cell_width + 'px'
);
b_dim = {
w: $('#' + this.id + ' .sudoku_board').width(),
h: $('#' + this.id + ' .sudoku_board').width(),
};
b_pos = $('#' + this.id + ' .sudoku_board').offset();
c_dim = {
w: $('#' + this.id + ' .board_console').width(),
h: $('#' + this.id + ' .board_console').height(),
};
var c_pos_new = {
left: b_dim.w / 2 - c_dim.w / 2 + b_pos.left,
top: b_dim.h / 2 - c_dim.h / 2 + b_pos.top,
};
$('#' + this.id + ' .board_console').css({
left: c_pos_new.left,
top: c_pos_new.top,
});
var gameover_pos_new = {
left: screen.w / 20,
top: screen.w / 20 + b_pos.top,
};
$('#' + this.id + ' .gameover').css({
left: gameover_pos_new.left,
top: gameover_pos_new.top,
});
console.log('screen', screen);
console.timeEnd('resizeWindow');
};
Sudoku.prototype.showConsole = function (cell) {
$('#' + this.id + ' .board_console_container').show();
var t = this,
oldNotes = $(this.cell).find('.note');
$('#' + t.id + ' .board_console .num').removeClass('selected');
if (t.markNotes) {
$('#' + t.id + ' .board_console .num.note').addClass('selected');
$.each(oldNotes, function () {
var noteNum = $(this).text();
$(
'#' + t.id + ' .board_console .num:contains(' + noteNum + ')'
).addClass('selected');
});
}
return this;
};
Sudoku.prototype.hideConsole = function (cell) {
$('#' + this.id + ' .board_console_container').hide();
return this;
};
Sudoku.prototype.cellSelect = function (cell) {
this.cell = cell;
var value = $(cell).text() | 0,
position = { x: $(cell).attr('x'), y: $(cell).attr('y') },
group_position = {
x: Math.floor((position.x - 1) / 3),
y: Math.floor((position.y - 1) / 3),
},
horizontal_cells = $(
'#' + this.id + ' .sudoku_board .cell[x="' + position.x + '"]'
),
vertical_cells = $(
'#' + this.id + ' .sudoku_board .cell[y="' + position.y + '"]'
),
group_cells = $(
'#' +
this.id +
' .sudoku_board .cell[gr="' +
group_position.x +
'' +
group_position.y +
'"]'
),
same_value_cells = $(
'#' + this.id + ' .sudoku_board .cell span:contains(' + value + ')'
);
$('#' + this.id + ' .sudoku_board .cell').removeClass(
'selected current group'
);
$('#' + this.id + ' .sudoku_board .cell span').removeClass('samevalue');
$(cell).addClass('selected current');
if (this.highlight > 0) {
horizontal_cells.addClass('selected');
vertical_cells.addClass('selected');
group_cells.addClass('selected group');
same_value_cells.not($(cell).find('span')).addClass('samevalue');
}
if ($(this.cell).hasClass('fix')) {
$('#' + this.id + ' .board_console .num').addClass('no');
} else {
$('#' + this.id + ' .board_console .num').removeClass('no');
this.showConsole();
this.resizeWindow();
}
};
Sudoku.prototype.addValue = function (value) {
console.log('prepare for addValue', value);
var position = { x: $(this.cell).attr('x'), y: $(this.cell).attr('y') },
group_position = {
x: Math.floor((position.x - 1) / 3),
y: Math.floor((position.y - 1) / 3),
},
horizontal_cells =
'#' + this.id + ' .sudoku_board .cell[x="' + position.x + '"]',
vertical_cells =
'#' + this.id + ' .sudoku_board .cell[y="' + position.y + '"]',
group_cells =
'#' +
this.id +
' .sudoku_board .cell[gr="' +
group_position.x +
'' +
group_position.y +
'"]',
horizontal_cells_exists = $(
horizontal_cells + ' span:contains(' + value + ')'
),
vertical_cells_exists = $(
vertical_cells + ' span:contains(' + value + ')'
),
group_cells_exists = $(group_cells + ' span:contains(' + value + ')'),
horizontal_notes =
horizontal_cells + ' .note:contains(' + value + ')',
vertical_notes = vertical_cells + ' .note:contains(' + value + ')',
group_notes = group_cells + ' .note:contains(' + value + ')',
old_value = parseInt($(this.cell).not('.notvalid').text()) || 0;
if ($(this.cell).hasClass('fix')) {
return;
}
$(this.cell)
.find('span')
.text(value === 0 ? '' : value);
if (
this.cell !== null &&
(horizontal_cells_exists.length ||
vertical_cells_exists.length ||
group_cells_exists.length)
) {
if (old_value !== value) {
$(this.cell).addClass('notvalid');
} else {
$(this.cell).find('span').text('');
}
} else {
$(this.cell).removeClass('notvalid');
console.log('Value added ', value);
$(horizontal_notes).remove();
$(vertical_notes).remove();
$(group_notes).remove();
}
this.cellsComplete = $(
'#' + this.id + ' .sudoku_board .cell:not(.notvalid) span:not(:empty)'
).length;
console.log(
'is game over? ',
this.cellsComplete,
this.cellsNr,
this.cellsComplete === this.cellsNr
);
if (this.cellsComplete === this.cellsNr) {
this.gameOver();
}
$('#' + this.id + ' .statistics .cells_complete').text(
'' + this.cellsComplete + '/' + this.cellsNr
);
return this;
};
Sudoku.prototype.addNote = function (value) {
console.log('addNote', value);
var t = this,
oldNotes = $(t.cell).find('.note'),
note_width = Math.floor($(t.cell).width() / 2);
if (oldNotes.length < 4) {
$('<div></div>')
.addClass('note')
.css({
'line-height': note_width + 'px',
height: note_width - 1,
width: note_width - 1,
})
.text(value)
.appendTo(this.cell);
}
return this;
};
Sudoku.prototype.removeNote = function (value) {
if (value === 0) {
$(this.cell).find('.note').remove();
} else {
$(this.cell)
.find('.note:contains(' + value + ')')
.remove();
}
return this;
};
Sudoku.prototype.gameOver = function () {
console.log('GAME OVER!');
this.status = this.END;
$('#' + this.id + ' .gameover_container').show();
};
Sudoku.prototype.run = function () {
this.status = this.RUNNING;
var t = this;
this.drawBoard();
$('#' + this.id + ' .sudoku_board .cell').on('click', function (e) {
t.cellSelect(this);
});
$('#' + this.id + ' .board_console .num').on('click', function (e) {
var value = $.isNumeric($(this).text())
? parseInt($(this).text())
: 0,
clickMarkNotes = $(this).hasClass('note'),
clickRemove = $(this).hasClass('remove'),
numSelected = $(this).hasClass('selected');
if (clickMarkNotes) {
console.log('clickMarkNotes');
t.markNotes = !t.markNotes;
if (t.markNotes) {
$(this).addClass('selected');
} else {
$(this).removeClass('selected');
t.removeNote(0).showConsole();
}
} else {
if (t.markNotes) {
if (!numSelected) {
if (!value) {
t.removeNote(0).hideConsole();
} else {
t.addValue(0).addNote(value).hideConsole();
}
} else {
t.removeNote(value).hideConsole();
}
} else {
t.removeNote(0).addValue(value).hideConsole();
}
}
});
$('#' + this.id + ' .board_console_container').on(
'click',
function (e) {
if ($(e.target).is('.board_console_container')) {
$(this).hide();
}
}
);
$(window).resize(function () {
t.resizeWindow();
});
};
$(function () {
console.time('loading time');
$('head').append(
'<meta name="viewport" content="initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,width=device-width,height=device-height,target-densitydpi=device-dpi,user-scalable=yes" />'
);
var game = new Sudoku({
id: 'sudoku_container',
fixCellsNr: 30,
highlight: 1,
displayTitle: 1,
});
game.run();
$('#sidebar-toggle').on('click', function (e) {
$('#sudoku_menu').toggleClass('open-sidebar');
});
$('#' + game.id + ' .restart').on('click', function () {
game.init().run();
});
$('#sudoku_menu .restart').on('click', function () {
game.init().run();
$('#sudoku_menu').removeClass('open-sidebar');
});
console.timeEnd('loading time');
});
</script>
</body>
</html>