Repository navigation
Expand file tree
/
Copy pathpuzzle.js
More file actions
198 lines (169 loc) · 5.92 KB
/
Copy pathpuzzle.js
File metadata and controls
198 lines (169 loc) · 5.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// Puzzle Slider – 3x3 sliding tile puzzle with multiplayer support
const TILE_EMOJIS = ['🏰','🗡️','🛡️','🐉','🧙','🏹','⚔️','🔮'];
// Tile 0 = empty, tiles 1-8 mapped to emojis
let tiles = []; // current board state (0 = empty)
let moves = 0;
let timerInterval = null;
let seconds = 0;
let gameActive = false;
// Multiplayer
const playerCount = parseInt(localStorage.getItem('miniGamePlayers') || '1', 10);
let currentPlayer = 0;
let playerMoves = Array(playerCount).fill(0);
let playerTimes = Array(playerCount).fill(0);
let playerTimers = Array(playerCount).fill(null);
let gamePhase = 'playing'; // 'playing' | 'roundDone' | 'allDone'
const GOAL = [1, 2, 3, 4, 5, 6, 7, 8, 0]; // solved state
function initPuzzle() {
tiles = generateSolvable();
moves = 0;
seconds = 0;
gameActive = false;
currentPlayer = 0;
playerMoves = Array(playerCount).fill(0);
playerTimes = Array(playerCount).fill(0);
gamePhase = 'playing';
clearInterval(timerInterval);
document.getElementById('puzzle-moves').textContent = '0';
document.getElementById('puzzle-time').textContent = '0:00';
document.getElementById('puzzle-player').textContent = `P${currentPlayer + 1}`;
document.getElementById('puzzle-message').classList.add('hidden');
updatePlayerBanner();
render();
}
function generateSolvable() {
let arr;
do {
arr = shuffle([1, 2, 3, 4, 5, 6, 7, 8, 0]);
} while (!isSolvable(arr) || isGoal(arr));
return arr;
}
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function isSolvable(arr) {
// Count inversions; puzzle is solvable if inversion count is even (for 3x3)
let inv = 0;
const nums = arr.filter(n => n !== 0);
for (let i = 0; i < nums.length; i++)
for (let j = i + 1; j < nums.length; j++)
if (nums[i] > nums[j]) inv++;
return inv % 2 === 0;
}
function isGoal(arr) {
return arr.every((v, i) => v === GOAL[i]);
}
function render() {
const grid = document.getElementById('puzzle-grid');
grid.innerHTML = '';
tiles.forEach((val, idx) => {
const tile = document.createElement('div');
tile.className = 'puzzle-tile' + (val === 0 ? ' empty' : '');
if (val !== 0) {
// Check if in correct position
if (val === GOAL[idx]) tile.classList.add('correct');
tile.innerHTML = `<span>${TILE_EMOJIS[val - 1]}</span><span class="tile-num">${val}</span>`;
tile.addEventListener('click', () => handleTileClick(idx));
}
grid.appendChild(tile);
});
}
function handleTileClick(idx) {
if (!gameActive && tiles.some(v => v === 0)) {
// Auto-start on first move
gameActive = true;
startPlayerTimer();
}
if (!gameActive || gamePhase !== 'playing') return;
const emptyIdx = tiles.indexOf(0);
if (!isAdjacent(idx, emptyIdx)) return;
[tiles[idx], tiles[emptyIdx]] = [tiles[emptyIdx], tiles[idx]];
playerMoves[currentPlayer]++;
document.getElementById('puzzle-moves').textContent = playerMoves[currentPlayer];
render();
if (isGoal(tiles)) {
endRound();
}
}
function isAdjacent(a, b) {
const row = [Math.floor(a / 3), Math.floor(b / 3)];
const col = [a % 3, b % 3];
return (row[0] === row[1] && Math.abs(col[0] - col[1]) === 1) ||
(col[0] === col[1] && Math.abs(row[0] - row[1]) === 1);
}
function startPlayerTimer() {
clearInterval(timerInterval);
const startTime = Date.now() - playerTimes[currentPlayer] * 1000;
timerInterval = setInterval(() => {
const elapsed = Math.floor((Date.now() - startTime) / 1000);
playerTimes[currentPlayer] = elapsed;
document.getElementById('puzzle-time').textContent = formatTime(elapsed);
}, 500);
}
function formatTime(s) {
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
}
function endRound() {
clearInterval(timerInterval);
gameActive = false;
if (playerCount === 1) {
const msg = document.getElementById('puzzle-message');
msg.classList.remove('hidden');
msg.textContent = `🎉 Solved! ${playerMoves[0]} moves in ${formatTime(playerTimes[0])}!`;
return;
}
// Multiplayer: next player gets a fresh scrambled board
const nextPlayer = currentPlayer + 1;
if (nextPlayer < playerCount) {
const msg = document.getElementById('puzzle-message');
msg.classList.remove('hidden');
msg.textContent = `✅ P${currentPlayer + 1} solved in ${playerMoves[currentPlayer]} moves! Pass to P${nextPlayer + 1}…`;
currentPlayer = nextPlayer;
document.getElementById('puzzle-player').textContent = `P${currentPlayer + 1}`;
updatePlayerBanner();
setTimeout(() => {
msg.classList.add('hidden');
tiles = generateSolvable();
document.getElementById('puzzle-moves').textContent = '0';
document.getElementById('puzzle-time').textContent = '0:00';
gameActive = false;
render();
}, 2500);
} else {
// All players done – show results
showFinalResults();
}
}
function showFinalResults() {
gamePhase = 'allDone';
let best = { moves: Infinity, player: -1 };
playerMoves.forEach((m, i) => {
if (m < best.moves) { best.moves = m; best.player = i; }
});
let text = `🏆 Quest Complete!\n`;
playerMoves.forEach((m, i) => {
text += `P${i + 1}: ${m} moves (${formatTime(playerTimes[i])}) `;
});
text += `\n🥇 Winner: P${best.player + 1}!`;
const msg = document.getElementById('puzzle-message');
msg.classList.remove('hidden');
msg.textContent = text;
msg.style.whiteSpace = 'pre-line';
}
function updatePlayerBanner() {
const banner = document.getElementById('puzzle-player-banner');
if (playerCount > 1) {
banner.classList.remove('hidden');
const colors = ['🟡', '🔵', '🔴', '🟢'];
banner.textContent = `${colors[currentPlayer]} Player ${currentPlayer + 1}'s Turn`;
} else {
banner.classList.add('hidden');
}
}
document.getElementById('btn-new-puzzle').addEventListener('click', initPuzzle);
initPuzzle();