11.9 脚本网页 数独
提示</button><button class="btn btn-info" id="aiSolveBtn">AI解题</button><button class="btn btn-secondary" id="undoBtn">撤销


<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能数独大师 - 多规则版</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
transition: background 1s ease;
overflow-x: hidden;
padding: 10px;
}
/* 背景动画粒子 */
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
z-index: 0;
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
animation: float 15s infinite linear;
}
@keyframes float {
from {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
to {
transform: translateY(-100vh) rotate(360deg);
opacity: 0;
}
}
.container {
position: relative;
z-index: 1;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
max-width: 1200px;
width: 100%;
animation: slideIn 0.5s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
header {
text-align: center;
margin-bottom: 20px;
}
h1 {
font-size: 2em;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.game-area {
display: flex;
gap: 20px;
flex-wrap: wrap;
justify-content: center;
}
.sudoku-board {
position: relative;
}
.sudoku-grid {
display: grid;
grid-template-columns: repeat(9, 1fr);
gap: 0;
border: 3px solid #333;
background: #fff;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.cell {
width: 40px;
height: 40px;
border: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
background: white;
}
.cell:hover {
background: #f0f0f0;
transform: scale(1.05);
z-index: 10;
}
.cell.selected {
background: #e3f2fd;
box-shadow: inset 0 0 0 2px #2196f3;
}
.cell.highlighted {
background: #fff3e0;
}
.cell.same-number {
background: #f3e5f5;
}
.cell.fixed {
background: #f5f5f5;
color: #333;
cursor: default;
font-weight: 900;
}
.cell.user-input {
color: #2196f3;
font-weight: 600;
background: #e8f5e9;
}
.cell.ai-solved {
color: #4caf50;
font-weight: 600;
}
.cell.error {
background: #ffebee;
color: #c62828;
animation: shake 0.5s;
}
.cell.correct {
animation: pulse 0.5s;
background: #e8f5e9;
}
/* 特殊规则样式 */
.cell.diagonal {
background: #fff8e1;
}
.cell.window {
background: #f3e5f5;
}
.cell.extra-area {
background: #e0f2f1;
}
.cell.odd {
background: #fce4ec;
}
.cell.even {
background: #e1f5fe;
}
.cell.greater-than::after,
.cell.less-than::after {
position: absolute;
font-size: 12px;
color: #666;
}
.cell.greater-than::after {
content: '>';
right: 2px;
top: 2px;
}
.cell.less-than::after {
content: '<';
left: 2px;
top: 2px;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
/* 3x3 区块边框 */
.cell:nth-child(3n) {
border-right: 2px solid #333;
}
.cell:nth-child(n+19):nth-child(-n+27),
.cell:nth-child(n+46):nth-child(-n+54) {
border-bottom: 2px solid #333;
}
.controls {
display: flex;
flex-direction: column;
gap: 15px;
min-width: 250px;
}
/* 规则选择器 */
.rule-selector {
position: relative;
margin-bottom: 15px;
}
.rule-dropdown {
width: 100%;
padding: 10px;
border: 2px solid #ddd;
border-radius: 8px;
background: white;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 20px;
padding-right: 40px;
}
.rule-dropdown:hover {
border-color: #667eea;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.2);
}
.rule-dropdown:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.rule-info {
margin-top: 5px;
padding: 8px;
background: #f5f5f5;
border-radius: 6px;
font-size: 12px;
color: #666;
display: none;
}
.rule-info.show {
display: block;
}
.difficulty-selector {
display: flex;
gap: 5px;
margin-bottom: 15px;
}
.difficulty-btn {
flex: 1;
padding: 8px;
border: 2px solid #ddd;
background: white;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
font-size: 12px;
}
.difficulty-btn.active {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border-color: #667eea;
}
.number-pad {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.num-btn {
padding: 12px;
font-size: 18px;
font-weight: bold;
border: none;
border-radius: 8px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.num-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
.num-btn:active {
transform: translateY(0);
}
.action-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.btn {
padding: 10px 15px;
border: none;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.btn-primary {
background: linear-gradient(135deg, #4caf50, #45a049);
color: white;
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.4);
}
.btn-secondary {
background: linear-gradient(135deg, #ff9800, #f57c00);
color: white;
box-shadow: 0 4px 15px rgba(255, 152, 0, 0.4);
}
.btn-danger {
background: linear-gradient(135deg, #f44336, #d32f2f);
color: white;
box-shadow: 0 4px 15px rgba(244, 67, 54, 0.4);
}
.btn-info {
background: linear-gradient(135deg, #2196f3, #1976d2);
color: white;
box-shadow: 0 4px 15px rgba(33, 150, 243, 0.4);
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
.stats {
background: linear-gradient(135deg, #f5f5f5, #e0e0e0);
padding: 15px;
border-radius: 10px;
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.1);
}
.stat-item {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
}
.stat-label {
color: #666;
font-weight: 600;
}
.stat-value {
color: #333;
font-weight: bold;
}
.theme-selector {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 8px;
margin-top: 15px;
}
.theme-btn {
width: 30px;
height: 30px;
border-radius: 50%;
border: 2px solid white;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.theme-btn:hover {
transform: scale(1.2);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.notes-mode {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: #f5f5f5;
border-radius: 8px;
}
.toggle-switch {
position: relative;
width: 50px;
height: 25px;
background: #ccc;
border-radius: 12px;
cursor: pointer;
transition: background 0.3s ease;
}
.toggle-switch.active {
background: #4caf50;
}
.toggle-slider {
position: absolute;
top: 2px;
left: 2px;
width: 21px;
height: 21px;
background: white;
border-radius: 50%;
transition: transform 0.3s ease;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.toggle-switch.active .toggle-slider {
transform: translateX(25px);
}
.message {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
background: white;
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
z-index: 1000;
animation: slideInRight 0.5s ease;
display: none;
font-size: 14px;
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.message.success {
border-left: 5px solid #4caf50;
}
.message.error {
border-left: 5px solid #f44336;
}
.message.info {
border-left: 5px solid #2196f3;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-top: 8px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4caf50, #8bc34a);
transition: width 0.5s ease;
border-radius: 4px;
}
/* 移动端优化 */
@media (max-width: 768px) {
.container {
padding: 15px;
}
h1 {
font-size: 1.5em;
}
.game-area {
flex-direction: column;
gap: 15px;
}
.cell {
width: 32px;
height: 32px;
font-size: 14px;
}
.controls {
min-width: 100%;
}
.number-pad {
gap: 5px;
}
.num-btn {
padding: 10px;
font-size: 16px;
}
.action-buttons {
grid-template-columns: repeat(3, 1fr);
gap: 5px;
}
.btn {
padding: 8px 10px;
font-size: 11px;
}
.difficulty-selector {
gap: 3px;
}
.difficulty-btn {
padding: 6px;
font-size: 11px;
}
.theme-selector {
grid-template-columns: repeat(4, 1fr);
gap: 5px;
}
.theme-btn {
width: 25px;
height: 25px;
}
.stats {
padding: 10px;
}
.stat-item {
font-size: 12px;
margin-bottom: 5px;
}
.notes-mode {
padding: 8px;
font-size: 14px;
}
.rule-dropdown {
font-size: 12px;
padding: 8px;
}
}
@media (max-width: 480px) {
.cell {
width: 28px;
height: 28px;
font-size: 12px;
}
.action-buttons {
grid-template-columns: repeat(2, 1fr);
}
.theme-selector {
grid-template-columns: repeat(3, 1fr);
}
}
</style>
</head>
<body>
<div class="particles" id="particles"></div>
<div class="container">
<header>
<h1>🎮 智能数独大师</h1>
<p style="color: #666; font-size: 14px;">多规则挑战,AI辅助解题</p>
</header>
<div class="game-area">
<div class="sudoku-board">
<div class="sudoku-grid" id="sudokuGrid"></div>
</div>
<div class="controls">
<!-- 规则选择器 -->
<div class="rule-selector">
<select class="rule-dropdown" id="ruleSelector">
<option value="standard">🎯 标准数独</option>
<option value="diagonal">🔸 对角线数独</option>
<option value="window">🪟 窗口数独</option>
<option value="extra">🎨 额外区域数独</option>
<option value="odd-even">⚡ 奇偶数独</option>
<option value="consecutive">🔗 连续数独</option>
<option value="inequality">📊 不等号数独</option>
<option value="killer">🔪 杀手数独</option>
<option value="irregular">🌀 不规则数独</option>
<option value="center">⭐ 中心数独</option>
<option value="color">🌈 颜色数独</option>
<option value="mini">📱 迷你数独 (6x6)</option>
<option value="super">🏆 超级数独 (16x16)</option>
<option value="arrow">➡️ 箭头数独</option>
<option value="thermo">🌡️ 温度计数独</option>
</select>
<div class="rule-info" id="ruleInfo"></div>
</div>
<div class="difficulty-selector">
<button class="difficulty-btn active" data-level="easy">简单</button>
<button class="difficulty-btn" data-level="medium">中等</button>
<button class="difficulty-btn" data-level="hard">困难</button>
<button class="difficulty-btn" data-level="expert">专家</button>
</div>
<div class="number-pad">
<button class="num-btn" data-num="1">1</button>
<button class="num-btn" data-num="2">2</button>
<button class="num-btn" data-num="3">3</button>
<button class="num-btn" data-num="4">4</button>
<button class="num-btn" data-num="5">5</button>
<button class="num-btn" data-num="6">6</button>
<button class="num-btn" data-num="7">7</button>
<button class="num-btn" data-num="8">8</button>
<button class="num-btn" data-num="9">9</button>
</div>
<div class="action-buttons">
<button class="btn btn-primary" id="newGameBtn">新游戏</button>
<button class="btn btn-secondary" id="hintBtn">提示</button>
<button class="btn btn-info" id="aiSolveBtn">AI解题</button>
<button class="btn btn-danger" id="clearBtn">清除</button>
<button class="btn btn-secondary" id="undoBtn">撤销</button>
<button class="btn btn-secondary" id="redoBtn">重做</button>
<button class="btn btn-primary" id="checkBtn">检查</button>
<button class="btn btn-info" id="pauseBtn">暂停</button>
</div>
<div class="notes-mode">
<span style="font-size: 14px;">笔记模式:</span>
<div class="toggle-switch" id="notesToggle">
<div class="toggle-slider"></div>
</div>
</div>
<div class="stats">
<div class="stat-item">
<span class="stat-label">⏱️ 时间:</span>
<span class="stat-value" id="timer">00:00</span>
</div>
<div class="stat-item">
<span class="stat-label">💡 提示:</span>
<span class="stat-value" id="hints">3</span>
</div>
<div class="stat-item">
<span class="stat-label">❌ 错误:</span>
<span class="stat-value" id="mistakes">0/3</span>
</div>
<div class="stat-item">
<span class="stat-label">📊 进度:</span>
<span class="stat-value" id="progress">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressBar" style="width: 0%"></div>
</div>
</div>
<div class="theme-selector">
<div class="theme-btn" style="background: linear-gradient(135deg, #667eea, #764ba2)" data-theme="purple"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #f093fb, #f5576c)" data-theme="pink"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #4facfe, #00f2fe)" data-theme="blue"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #43e97b, #38f9d7)" data-theme="green"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #fa709a, #fee140)" data-theme="sunset"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #30cfd0, #330867)" data-theme="ocean"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #a8edea, #fed6e3)" data-theme="pastel"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #ff9a9e, #fecfef)" data-theme="coral"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #ffecd2, #fcb69f)" data-theme="peach"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #a1c4fd, #c2e9fb)" data-theme="sky"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #d299c2, #fef9d7)" data-theme="lavender"></div>
<div class="theme-btn" style="background: linear-gradient(135deg, #89f7fe, #66a6ff)" data-theme="cyan"></div>
</div>
</div>
</div>
</div>
<div class="message" id="message"></div>
<script>
class SudokuGame {
constructor() {
this.grid = [];
this.solution = [];
this.initialGrid = [];
this.selectedCell = null;
this.difficulty = 'easy';
this.notesMode = false;
this.hints = 3;
this.mistakes = 0;
this.history = [];
this.historyIndex = -1;
this.timer = 0;
this.timerInterval = null;
this.isPaused = false;
this.aiSolving = false;
this.currentRule = 'standard';
this.gridSize = 9;
this.rules = {
standard: {
name: '标准数独',
description: '每行、每列、每个3x3宫格内数字1-9不重复',
gridSize: 9
},
diagonal: {
name: '对角线数独',
description: '标准规则 + 两条对角线数字1-9不重复',
gridSize: 9
},
window: {
name: '窗口数独',
description: '标准规则 + 四个2x2窗口内数字不重复',
gridSize: 9
},
extra: {
name: '额外区域数独',
description: '标准规则 + 指定额外区域内数字不重复',
gridSize: 9
},
'odd-even': {
name: '奇偶数独',
description: '标准规则 + 指定格子只能是奇数或偶数',
gridSize: 9
},
consecutive: {
name: '连续数独',
description: '标准规则 + 相邻标记格子数字差为1',
gridSize: 9
},
inequality: {
name: '不等号数独',
description: '标准规则 + 相邻格子有大小关系约束',
gridSize: 9
},
killer: {
name: '杀手数独',
description: '标准规则 + 虚线框内数字和等于指定值',
gridSize: 9
},
irregular: {
name: '不规则数独',
description: '每行、每列数字不重复,但宫格形状不规则',
gridSize: 9
},
center: {
name: '中心数独',
description: '标准规则 + 中心3x3区域数字不重复',
gridSize: 9
},
color: {
name: '颜色数独',
description: '标准规则 + 相同颜色格子数字不重复',
gridSize: 9
},
mini: {
name: '迷你数独',
description: '6x6网格,每行、每列、每个2x3宫格内数字1-6不重复',
gridSize: 6
},
super: {
name: '超级数独',
description: '16x16网格,使用数字1-9和字母A-G',
gridSize: 16
},
arrow: {
name: '箭头数独',
description: '标准规则 + 箭头指向格子数字和等于箭头起点数字',
gridSize: 9
},
thermo: {
name: '温度计数独',
description: '标准规则 + 温度计上数字递增',
gridSize: 9
}
};
this.themes = {
purple: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
pink: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
blue: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
green: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
sunset: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)',
ocean: 'linear-gradient(135deg, #30cfd0 0%, #330867 100%)',
pastel: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)',
coral: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%)',
peach: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)',
sky: 'linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%)',
lavender: 'linear-gradient(135deg, #d299c2 0%, #fef9d7 100%)',
cyan: 'linear-gradient(135deg, #89f7fe 0%, #66a6ff 100%)'
};
this.init();
}
init() {
this.setupRuleSelector();
this.createGrid();
this.setupEventListeners();
this.createParticles();
this.newGame();
this.startTimer();
}
setupRuleSelector() {
const ruleSelector = document.getElementById('ruleSelector');
const ruleInfo = document.getElementById('ruleInfo');
ruleSelector.addEventListener('change', (e) => {
this.currentRule = e.target.value;
const rule = this.rules[this.currentRule];
ruleInfo.textContent = rule.description;
ruleInfo.classList.add('show');
setTimeout(() => {
ruleInfo.classList.remove('show');
}, 3000);
this.newGame();
});
}
createGrid() {
const gridElement = document.getElementById('sudokuGrid');
gridElement.innerHTML = '';
const size = this.gridSize;
gridElement.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
for (let i = 0; i < size * size; i++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.index = i;
cell.dataset.row = Math.floor(i / size);
cell.dataset.col = i % size;
if (size === 9) {
cell.dataset.box = Math.floor((Math.floor(i / 9) / 3)) * 3 + Math.floor((i % 9) / 3);
} else if (size === 6) {
cell.dataset.box = Math.floor((Math.floor(i / 6) / 2)) * 3 + Math.floor((i % 6) / 3);
} else if (size === 16) {
cell.dataset.box = Math.floor((Math.floor(i / 16) / 4)) * 4 + Math.floor((i % 16) / 4);
}
// 应用特殊规则样式
this.applyRuleStyles(cell, i);
gridElement.appendChild(cell);
}
}
applyRuleStyles(cell, index) {
const row = Math.floor(index / this.gridSize);
const col = index % this.gridSize;
switch(this.currentRule) {
case 'diagonal':
if (row === col || row + col === this.gridSize - 1) {
cell.classList.add('diagonal');
}
break;
case 'window':
if ((row >= 1 && row <= 2 && col >= 1 && col <= 2) ||
(row >= 1 && row <= 2 && col >= 6 && col <= 7) ||
(row >= 6 && row <= 7 && col >= 1 && col <= 2) ||
(row >= 6 && row <= 7 && col >= 6 && col <= 7)) {
cell.classList.add('window');
}
break;
case 'center':
if (row >= 3 && row <= 5 && col >= 3 && col <= 5) {
cell.classList.add('center');
}
break;
case 'odd-even':
if ((row + col) % 2 === 0) {
cell.classList.add('even');
} else {
cell.classList.add('odd');
}
break;
case 'color':
const colorIndex = Math.floor(row / 3) * 3 + Math.floor(col / 3);
if (colorIndex % 2 === 0) {
cell.classList.add('extra-area');
}
break;
case 'inequality':
if (Math.random() > 0.7) {
if (Math.random() > 0.5) {
cell.classList.add('greater-than');
} else {
cell.classList.add('less-than');
}
}
break;
}
}
setupEventListeners() {
// 单元格点击
document.getElementById('sudokuGrid').addEventListener('click', (e) => {
if (e.target.classList.contains('cell') && !e.target.classList.contains('fixed')) {
this.selectCell(e.target);
}
});
// 数字按钮
document.querySelectorAll('.num-btn').forEach(btn => {
btn.addEventListener('click', () => {
const num = parseInt(btn.dataset.num);
this.inputNumber(num);
});
});
// 键盘输入
document.addEventListener('keydown', (e) => {
if (this.aiSolving) return;
let num = null;
if (this.gridSize === 6) {
if (e.key >= '1' && e.key <= '6') {
num = parseInt(e.key);
}
} else if (this.gridSize === 16) {
if (e.key >= '1' && e.key <= '9') {
num = parseInt(e.key);
} else if (e.key >= 'a' && e.key <= 'g') {
num = e.key.toUpperCase();
}
} else {
if (e.key >= '1' && e.key <= '9') {
num = parseInt(e.key);
}
}
if (num !== null) {
this.inputNumber(num);
} else if (e.key === 'Delete' || e.key === 'Backspace') {
this.clearCell();
} else if (e.key === 'ArrowUp' || e.key === 'ArrowDown' ||
e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
this.navigateWithArrows(e.key);
}
});
// 功能按钮
document.getElementById('newGameBtn').addEventListener('click', () => this.newGame());
document.getElementById('hintBtn').addEventListener('click', () => this.giveHint());
document.getElementById('aiSolveBtn').addEventListener('click', () => this.aiSolve());
document.getElementById('clearBtn').addEventListener('click', () => this.clearCell());
document.getElementById('undoBtn').addEventListener('click', () => this.undo());
document.getElementById('redoBtn').addEventListener('click', () => this.redo());
document.getElementById('checkBtn').addEventListener('click', () => this.checkSolution());
document.getElementById('pauseBtn').addEventListener('click', () => this.togglePause());
// 难度选择
document.querySelectorAll('.difficulty-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.difficulty-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.difficulty = btn.dataset.level;
this.newGame();
});
});
// 笔记模式
document.getElementById('notesToggle').addEventListener('click', () => {
this.notesMode = !this.notesMode;
document.getElementById('notesToggle').classList.toggle('active');
});
// 主题选择
document.querySelectorAll('.theme-btn').forEach(btn => {
btn.addEventListener('click', () => {
const theme = btn.dataset.theme;
document.body.style.background = this.themes[theme];
this.showMessage('主题已切换', 'success');
});
});
}
createParticles() {
const particlesContainer = document.getElementById('particles');
for (let i = 0; i < 30; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = Math.random() * 100 + '%';
particle.style.animationDelay = Math.random() * 15 + 's';
particle.style.animationDuration = (15 + Math.random() * 10) + 's';
particlesContainer.appendChild(particle);
}
}
newGame() {
this.aiSolving = false;
const rule = this.rules[this.currentRule];
this.gridSize = rule.gridSize;
// 更新数字按钮
this.updateNumberPad();
this.generatePuzzle();
this.renderGrid();
this.hints = 3;
this.mistakes = 0;
this.timer = 0;
this.history = [];
this.historyIndex = -1;
this.updateStats();
this.showMessage(`新游戏开始!${rule.name}`, 'success');
}
updateNumberPad() {
const numberPad = document.querySelector('.number-pad');
numberPad.innerHTML = '';
if (this.gridSize === 6) {
for (let i = 1; i <= 6; i++) {
const btn = document.createElement('button');
btn.className = 'num-btn';
btn.dataset.num = i;
btn.textContent = i;
btn.addEventListener('click', () => this.inputNumber(i));
numberPad.appendChild(btn);
}
} else if (this.gridSize === 16) {
for (let i = 1; i <= 9; i++) {
const btn = document.createElement('button');
btn.className = 'num-btn';
btn.dataset.num = i;
btn.textContent = i;
btn.addEventListener('click', () => this.inputNumber(i));
numberPad.appendChild(btn);
}
for (let i = 65; i <= 71; i++) {
const btn = document.createElement('button');
btn.className = 'num-btn';
btn.dataset.num = String.fromCharCode(i);
btn.textContent = String.fromCharCode(i);
btn.addEventListener('click', () => this.inputNumber(String.fromCharCode(i)));
numberPad.appendChild(btn);
}
} else {
for (let i = 1; i <= 9; i++) {
const btn = document.createElement('button');
btn.className = 'num-btn';
btn.dataset.num = i;
btn.textContent = i;
btn.addEventListener('click', () => this.inputNumber(i));
numberPad.appendChild(btn);
}
}
}
generatePuzzle() {
const totalCells = this.gridSize * this.gridSize;
// 生成完整的数独解
this.solution = this.generateCompleteSudoku();
// 根据难度移除数字
const cellsToRemove = {
easy: Math.floor(totalCells * 0.4),
medium: Math.floor(totalCells * 0.5),
hard: Math.floor(totalCells * 0.6),
expert: Math.floor(totalCells * 0.7)
}[this.difficulty];
this.grid = [...this.solution];
const positions = Array.from({length: totalCells}, (_, i) => i);
for (let i = 0; i < cellsToRemove; i++) {
const randomIndex = Math.floor(Math.random() * positions.length);
const position = positions.splice(randomIndex, 1)[0];
this.grid[position] = 0;
}
this.initialGrid = [...this.grid];
}
generateCompleteSudoku() {
const grid = Array(this.gridSize * this.gridSize).fill(0);
this.fillGrid(grid);
return grid;
}
fillGrid(grid) {
const numbers = [];
if (this.gridSize === 6) {
numbers.push(...[1, 2, 3, 4, 5, 6]);
} else if (this.gridSize === 16) {
numbers.push(...[1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F', 'G']);
} else {
numbers.push(...[1, 2, 3, 4, 5, 6, 7, 8, 9]);
}
for (let i = 0; i < this.gridSize * this.gridSize; i++) {
if (grid[i] === 0) {
const shuffled = this.shuffle([...numbers]);
for (let num of shuffled) {
if (this.isValidMove(grid, i, num)) {
grid[i] = num;
if (this.fillGrid(grid)) {
return true;
}
grid[i] = 0;
}
}
return false;
}
}
return true;
}
shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
isValidMove(grid, index, num) {
const row = Math.floor(index / this.gridSize);
const col = index % this.gridSize;
// 检查行
for (let i = 0; i < this.gridSize; i++) {
if (grid[row * this.gridSize + i] === num) return false;
}
// 检查列
for (let i = 0; i < this.gridSize; i++) {
if (grid[i * this.gridSize + col] === num) return false;
}
// 检查宫格
let boxSize;
if (this.gridSize === 6) {
boxSize = 2;
} else if (this.gridSize === 16) {
boxSize = 4;
} else {
boxSize = 3;
}
const boxRow = Math.floor(row / boxSize) * boxSize;
const boxCol = Math.floor(col / boxSize) * boxSize;
for (let i = 0; i < boxSize; i++) {
for (let j = 0; j < boxSize; j++) {
if (grid[(boxRow + i) * this.gridSize + (boxCol + j)] === num) return false;
}
}
// 特殊规则检查
return this.checkSpecialRules(grid, index, num, row, col);
}
checkSpecialRules(grid, index, num, row, col) {
switch(this.currentRule) {
case 'diagonal':
// 检查对角线
if (row === col) {
for (let i = 0; i < this.gridSize; i++) {
if (grid[i * this.gridSize + i] === num) return false;
}
}
if (row + col === this.gridSize - 1) {
for (let i = 0; i < this.gridSize; i++) {
if (grid[i * this.gridSize + (this.gridSize - 1 - i)] === num) return false;
}
}
break;
case 'center':
// 检查中心区域
if (row >= 3 && row <= 5 && col >= 3 && col <= 5) {
for (let i = 3; i <= 5; i++) {
for (let j = 3; j <= 5; j++) {
if (grid[i * this.gridSize + j] === num) return false;
}
}
}
break;
case 'odd-even':
// 检查奇偶约束
const cell = document.querySelector(`.cell[data-index="${index}"]`);
if (cell.classList.contains('odd') && num % 2 === 0) return false;
if (cell.classList.contains('even') && num % 2 === 1) return false;
break;
}
return true;
}
renderGrid() {
const cells = document.querySelectorAll('.cell');
cells.forEach((cell, index) => {
cell.textContent = this.grid[index] || '';
cell.classList.remove('fixed', 'error', 'selected', 'highlighted', 'same-number', 'user-input', 'ai-solved');
if (this.initialGrid[index] !== 0) {
cell.classList.add('fixed');
} else if (this.grid[index] !== 0) {
if (this.aiSolving) {
cell.classList.add('ai-solved');
} else {
cell.classList.add('user-input');
}
}
});
}
selectCell(cell) {
if (this.aiSolving) return;
document.querySelectorAll('.cell').forEach(c => {
c.classList.remove('selected', 'highlighted', 'same-number');
});
cell.classList.add('selected');
this.selectedCell = cell;
const index = parseInt(cell.dataset.index);
const row = parseInt(cell.dataset.row);
const col = parseInt(cell.dataset.col);
const box = parseInt(cell.dataset.box);
const value = this.grid[index];
// 高亮同行、同列、同宫格
document.querySelectorAll('.cell').forEach(c => {
if (parseInt(c.dataset.row) === row ||
parseInt(c.dataset.col) === col ||
parseInt(c.dataset.box) === box) {
c.classList.add('highlighted');
}
// 高亮相同数字
if (value && this.grid[parseInt(c.dataset.index)] === value) {
c.classList.add('same-number');
}
});
}
inputNumber(num) {
if (!this.selectedCell || this.selectedCell.classList.contains('fixed') || this.aiSolving) return;
const index = parseInt(this.selectedCell.dataset.index);
if (this.notesMode) {
this.toggleNote(this.selectedCell, num);
} else {
this.saveHistory();
this.grid[index] = num;
this.selectedCell.textContent = num;
this.selectedCell.classList.remove('ai-solved');
this.selectedCell.classList.add('user-input');
if (num !== this.solution[index]) {
this.selectedCell.classList.add('error');
this.mistakes++;
this.updateStats();
if (this.mistakes >= 3) {
this.showMessage('游戏结束!错误次数过多', 'error');
setTimeout(() => this.newGame(), 2000);
}
} else {
this.selectedCell.classList.remove('error');
this.selectedCell.classList.add('correct');
setTimeout(() => {
this.selectedCell.classList.remove('correct');
}, 500);
}
this.updateProgress();
this.checkWin();
}
}
toggleNote(cell, num) {
const currentNotes = cell.dataset.notes || '';
const notes = currentNotes.split('').filter(n => n !== num.toString());
if (currentNotes.includes(num.toString())) {
cell.dataset.notes = notes.join('');
} else {
notes.push(num.toString());
cell.dataset.notes = notes.join('');
}
if (cell.dataset.notes) {
cell.innerHTML = `<small style="font-size: 10px; color: #666;">${cell.dataset.notes}</small>`;
} else {
cell.textContent = '';
}
}
clearCell() {
if (!this.selectedCell || this.selectedCell.classList.contains('fixed') || this.aiSolving) return;
this.saveHistory();
const index = parseInt(this.selectedCell.dataset.index);
this.grid[index] = 0;
this.selectedCell.textContent = '';
this.selectedCell.classList.remove('error', 'user-input', 'ai-solved');
delete this.selectedCell.dataset.notes;
this.updateProgress();
}
navigateWithArrows(key) {
if (!this.selectedCell || this.aiSolving) {
if (!this.aiSolving) {
this.selectCell(document.querySelector('.cell'));
}
return;
}
const index = parseInt(this.selectedCell.dataset.index);
const row = Math.floor(index / this.gridSize);
const col = index % this.gridSize;
let newIndex = index;
switch(key) {
case 'ArrowUp':
if (row > 0) newIndex = (row - 1) * this.gridSize + col;
break;
case 'ArrowDown':
if (row < this.gridSize - 1) newIndex = (row + 1) * this.gridSize + col;
break;
case 'ArrowLeft':
if (col > 0) newIndex = row * this.gridSize + (col - 1);
break;
case 'ArrowRight':
if (col < this.gridSize - 1) newIndex = row * this.gridSize + (col + 1);
break;
}
const newCell = document.querySelector(`.cell[data-index="${newIndex}"]`);
if (newCell) this.selectCell(newCell);
}
giveHint() {
if (this.hints <= 0) {
this.showMessage('没有提示了!', 'error');
return;
}
const emptyCells = [];
this.grid.forEach((val, index) => {
if (val === 0) emptyCells.push(index);
});
if (emptyCells.length === 0) return;
const randomIndex = emptyCells[Math.floor(Math.random() * emptyCells.length)];
const cell = document.querySelector(`.cell[data-index="${randomIndex}"]`);
this.grid[randomIndex] = this.solution[randomIndex];
cell.textContent = this.solution[randomIndex];
cell.classList.add('correct');
cell.classList.add('user-input');
setTimeout(() => {
cell.classList.remove('correct');
}, 1000);
this.hints--;
this.updateStats();
this.updateProgress();
this.checkWin();
this.showMessage('使用了一个提示!', 'info');
}
aiSolve() {
if (this.aiSolving) {
this.showMessage('AI正在解题中...', 'info');
return;
}
this.aiSolving = true;
this.showMessage('AI正在解题...', 'info');
const emptyCells = [];
this.grid.forEach((val, index) => {
if (val === 0) emptyCells.push(index);
});
let currentIndex = 0;
const solveInterval = setInterval(() => {
if (currentIndex >= emptyCells.length || !this.aiSolving) {
clearInterval(solveInterval);
this.aiSolving = false;
if (currentIndex >= emptyCells.length) {
this.showMessage('AI解题完成!', 'success');
}
return;
}
const index = emptyCells[currentIndex];
const cell = document.querySelector(`.cell[data-index="${index}"]`);
this.grid[index] = this.solution[index];
cell.textContent = this.solution[index];
cell.classList.add('correct');
cell.classList.add('ai-solved');
setTimeout(() => {
cell.classList.remove('correct');
}, 500);
currentIndex++;
}, 300);
this.updateProgress();
}
checkSolution() {
let hasErrors = false;
document.querySelectorAll('.cell').forEach((cell, index) => {
if (this.grid[index] !== 0 && this.grid[index] !== this.solution[index]) {
cell.classList.add('error');
hasErrors = true;
} else {
cell.classList.remove('error');
}
});
if (hasErrors) {
this.showMessage('发现错误!', 'error');
} else {
this.showMessage('目前没有错误!', 'success');
}
}
checkWin() {
const isComplete = this.grid.every((val, index) => val === this.solution[index]);
if (isComplete) {
this.stopTimer();
this.showMessage(`🎉 恭喜完成!用时 ${this.formatTime(this.timer)}`, 'success');
document.querySelectorAll('.cell').forEach((cell, index) => {
setTimeout(() => {
cell.classList.add('correct');
}, index * 10);
});
}
}
saveHistory() {
this.history = this.history.slice(0, this.historyIndex + 1);
this.history.push([...this.grid]);
this.historyIndex++;
}
undo() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.grid = [...this.history[this.historyIndex]];
this.renderGrid();
this.updateProgress();
}
}
redo() {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
this.grid = [...this.history[this.historyIndex]];
this.renderGrid();
this.updateProgress();
}
}
togglePause() {
this.isPaused = !this.isPaused;
const btn = document.getElementById('pauseBtn');
if (this.isPaused) {
this.stopTimer();
btn.textContent = '继续';
document.getElementById('sudokuGrid').style.opacity = '0.3';
this.showMessage('游戏已暂停', 'info');
} else {
this.startTimer();
btn.textContent = '暂停';
document.getElementById('sudokuGrid').style.opacity = '1';
this.showMessage('游戏继续', 'info');
}
}
startTimer() {
this.timerInterval = setInterval(() => {
this.timer++;
document.getElementById('timer').textContent = this.formatTime(this.timer);
}, 1000);
}
stopTimer() {
clearInterval(this.timerInterval);
}
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
updateStats() {
document.getElementById('hints').textContent = this.hints;
document.getElementById('mistakes').textContent = `${this.mistakes}/3`;
}
updateProgress() {
const filled = this.grid.filter((val, index) => val === this.solution[index]).length;
const total = this.gridSize * this.gridSize;
const progress = Math.round((filled / total) * 100);
document.getElementById('progress').textContent = `${progress}%`;
document.getElementById('progressBar').style.width = `${progress}%`;
}
showMessage(text, type) {
const messageEl = document.getElementById('message');
messageEl.textContent = text;
messageEl.className = `message ${type}`;
messageEl.style.display = 'block';
setTimeout(() => {
messageEl.style.display = 'none';
}, 3000);
}
}
// 启动游戏
const game = new SudokuGame();
// 自动切换背景主题
const themes = Object.keys(game.themes);
let currentThemeIndex = 0;
setInterval(() => {
currentThemeIndex = (currentThemeIndex + 1) % themes.length;
const theme = themes[currentThemeIndex];
document.body.style.background = game.themes[theme];
}, 30000);
</script>
</body>
</html>
更多推荐



所有评论(0)