KEXEL commited on
Commit
e31d625
·
verified ·
1 Parent(s): 3e57a04
Files changed (1) hide show
  1. VitaMahjong.html +719 -8
VitaMahjong.html CHANGED
@@ -86,11 +86,722 @@
86
  50% { transform: scale(1.05); }
87
  100% { transform: scale(1); }
88
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  </style>
90
  </head>
91
  <body class="bg-gray-100 min-h-screen font-sans">
92
  <div class="container mx-auto px-4 py-8">
93
- <!-- Header -->
94
  <header class="flex justify-between items-center mb-8">
95
  <div class="flex items-center">
96
  <i class="fas fa-dragon text-4xl text-purple-600 mr-3"></i>
@@ -112,7 +823,7 @@
112
  </div>
113
  </header>
114
 
115
- <!-- Game Controls -->
116
  <div class="flex justify-between mb-6">
117
  <div class="flex space-x-3">
118
  <button id="new-game" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg shadow flex items-center">
@@ -129,19 +840,19 @@
129
  </div>
130
  </div>
131
 
132
- <!-- Game Board -->
133
  <div class="board-container bg-white rounded-xl shadow-xl p-6 mb-6">
134
  <div id="board" class="grid grid-cols-8 gap-3 mx-auto"></div>
135
  </div>
136
 
137
- <!-- Game Status -->
138
  <div id="game-status" class="text-center mb-6 hidden">
139
  <div class="inline-block bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg">
140
  <p class="font-bold">Level Complete!</p>
141
  </div>
142
  </div>
143
 
144
- <!-- Level Complete Modal -->
145
  <div id="level-complete-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
146
  <div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full level-complete">
147
  <div class="text-center">
@@ -167,7 +878,7 @@
167
  </div>
168
  </div>
169
 
170
- <!-- Game Over Modal -->
171
  <div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
172
  <div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full">
173
  <div class="text-center">
@@ -194,7 +905,7 @@
194
  </div>
195
  </div>
196
 
197
- <!-- Audio elements for sound effects -->
198
  <audio id="flip-sound" src="https://kexel-git.static.hf.space/gsom/zapsplat_multimedia_button_click.mp3" preload="auto"></audio>
199
  <audio id="match-sound" src="https://kexel-git.static.hf.space/gsom/positive-beeps.mp3" preload="auto"></audio>
200
  <audio id="mismatch-sound" src="https://kexel-git.static.hf.space/gsom/mixkit-quick-jump-arcade-game-239.mp3" preload="auto"></audio>
@@ -659,7 +1370,7 @@
659
  </script>
660
  </body>
661
  </html>
662
-
663
 
664
 
665
  <!--<!DOCTYPE html>
 
86
  50% { transform: scale(1.05); }
87
  100% { transform: scale(1); }
88
  }
89
+
90
+ /* Mobile-specific styles */
91
+ @media (max-width: 640px) {
92
+ .tile-front span, .tile-back i {
93
+ font-size: 1rem !important;
94
+ }
95
+
96
+ .header-container {
97
+ flex-direction: column;
98
+ align-items: center;
99
+ gap: 1rem;
100
+ }
101
+
102
+ .stats-container {
103
+ width: 100%;
104
+ justify-content: space-between;
105
+ }
106
+
107
+ .controls-container {
108
+ flex-direction: column;
109
+ gap: 0.5rem;
110
+ }
111
+
112
+ .controls-container > div {
113
+ width: 100%;
114
+ }
115
+
116
+ .controls-container button {
117
+ width: 100%;
118
+ padding: 0.5rem;
119
+ }
120
+
121
+ .modal-content {
122
+ width: 90%;
123
+ padding: 1.5rem;
124
+ }
125
+ }
126
+ </style>
127
+ </head>
128
+ <body class="bg-gray-100 min-h-screen font-sans">
129
+ <div class="container mx-auto px-2 sm:px-4 py-4 sm:py-8">
130
+ <!-- Header - Made responsive with flex column on mobile -->
131
+ <header class="header-container flex flex-col sm:flex-row justify-between items-center mb-4 sm:mb-8">
132
+ <div class="flex items-center mb-2 sm:mb-0">
133
+ <i class="fas fa-dragon text-3xl sm:text-4xl text-purple-600 mr-2 sm:mr-3"></i>
134
+ <h1 class="text-2xl sm:text-3xl font-bold text-gray-800">Vita Mahjong</h1>
135
+ </div>
136
+ <div class="stats-container flex items-center space-x-2 sm:space-x-4 w-full sm:w-auto justify-center sm:justify-start">
137
+ <div class="bg-white rounded-lg shadow p-2 sm:p-3 flex items-center text-sm sm:text-base">
138
+ <i class="fas fa-clock text-purple-600 mr-1 sm:mr-2 text-sm sm:text-base"></i>
139
+ <span id="timer" class="font-bold">00:00</span>
140
+ </div>
141
+ <div class="bg-white rounded-lg shadow p-2 sm:p-3 flex items-center text-sm sm:text-base">
142
+ <i class="fas fa-layer-group text-purple-600 mr-1 sm:mr-2 text-sm sm:text-base"></i>
143
+ <span id="level" class="font-bold">Level 1</span>
144
+ </div>
145
+ <div class="bg-white rounded-lg shadow p-2 sm:p-3 flex items-center text-sm sm:text-base">
146
+ <i class="fas fa-star text-yellow-500 mr-1 sm:mr-2 text-sm sm:text-base"></i>
147
+ <span id="score" class="font-bold">0</span>
148
+ </div>
149
+ </div>
150
+ </header>
151
+
152
+ <!-- Game Controls - Stacked vertically on mobile -->
153
+ <div class="controls-container flex flex-col sm:flex-row justify-between mb-4 sm:mb-6">
154
+ <div class="flex space-x-2 sm:space-x-3 mb-2 sm:mb-0">
155
+ <button id="new-game" class="bg-purple-600 hover:bg-purple-700 text-white px-3 sm:px-4 py-1 sm:py-2 rounded-lg shadow flex items-center text-sm sm:text-base">
156
+ <i class="fas fa-plus-circle mr-1 sm:mr-2"></i> New Game
157
+ </button>
158
+ <button id="hint" class="bg-amber-500 hover:bg-amber-600 text-white px-3 sm:px-4 py-1 sm:py-2 rounded-lg shadow flex items-center text-sm sm:text-base">
159
+ <i class="fas fa-lightbulb mr-1 sm:mr-2"></i> Hint
160
+ </button>
161
+ </div>
162
+ <div>
163
+ <button id="sound-toggle" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-3 sm:px-4 py-1 sm:py-2 rounded-lg shadow flex items-center text-sm sm:text-base">
164
+ <i class="fas fa-volume-up mr-1 sm:mr-2"></i> Sound On
165
+ </button>
166
+ </div>
167
+ </div>
168
+
169
+ <!-- Game Board - Responsive grid with smaller tiles on mobile -->
170
+ <div class="board-container bg-white rounded-xl shadow-xl p-3 sm:p-6 mb-4 sm:mb-6 overflow-auto">
171
+ <div id="board" class="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-8 gap-2 sm:gap-3 mx-auto"></div>
172
+ </div>
173
+
174
+ <!-- Game Status -->
175
+ <div id="game-status" class="text-center mb-4 sm:mb-6 hidden">
176
+ <div class="inline-block bg-green-100 border-l-4 border-green-500 text-green-700 p-3 sm:p-4 rounded-lg text-sm sm:text-base">
177
+ <p class="font-bold">Level Complete!</p>
178
+ </div>
179
+ </div>
180
+
181
+ <!-- Level Complete Modal - Responsive sizing -->
182
+ <div id="level-complete-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50 p-2 sm:p-4">
183
+ <div class="modal-content bg-white rounded-xl shadow-2xl p-4 sm:p-8 w-full max-w-sm sm:max-w-md level-complete">
184
+ <div class="text-center">
185
+ <div class="text-4xl sm:text-6xl text-yellow-500 mb-2 sm:mb-4">
186
+ <i class="fas fa-trophy"></i>
187
+ </div>
188
+ <h2 class="text-xl sm:text-3xl font-bold text-gray-800 mb-1 sm:mb-2">Level Complete!</h2>
189
+ <p class="text-sm sm:text-base text-gray-600 mb-4 sm:mb-6">Great job! Ready for the next challenge?</p>
190
+ <div class="grid grid-cols-2 gap-2 sm:gap-4 mb-4 sm:mb-6">
191
+ <div class="bg-purple-50 rounded-lg p-2 sm:p-3">
192
+ <p class="text-xs sm:text-sm text-purple-600">Time</p>
193
+ <p id="level-time" class="font-bold text-lg sm:text-xl">00:45</p>
194
+ </div>
195
+ <div class="bg-purple-50 rounded-lg p-2 sm:p-3">
196
+ <p class="text-xs sm:text-sm text-purple-600">Score</p>
197
+ <p id="level-score" class="font-bold text-lg sm:text-xl">+250</p>
198
+ </div>
199
+ </div>
200
+ <button id="next-level" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 sm:py-3 rounded-lg shadow-lg font-bold text-sm sm:text-base">
201
+ Next Level <i class="fas fa-arrow-right ml-1 sm:ml-2"></i>
202
+ </button>
203
+ </div>
204
+ </div>
205
+ </div>
206
+
207
+ <!-- Game Over Modal - Responsive sizing -->
208
+ <div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50 p-2 sm:p-4">
209
+ <div class="modal-content bg-white rounded-xl shadow-2xl p-4 sm:p-8 w-full max-w-sm sm:max-w-md">
210
+ <div class="text-center">
211
+ <div class="text-4xl sm:text-6xl text-red-500 mb-2 sm:mb-4">
212
+ <i class="fas fa-gamepad"></i>
213
+ </div>
214
+ <h2 class="text-xl sm:text-3xl font-bold text-gray-800 mb-1 sm:mb-2">Game Over</h2>
215
+ <p class="text-sm sm:text-base text-gray-600 mb-4 sm:mb-6">Better luck next time!</p>
216
+ <div class="grid grid-cols-2 gap-2 sm:gap-4 mb-4 sm:mb-6">
217
+ <div class="bg-purple-50 rounded-lg p-2 sm:p-3">
218
+ <p class="text-xs sm:text-sm text-purple-600">Level Reached</p>
219
+ <p id="final-level" class="font-bold text-lg sm:text-xl">3</p>
220
+ </div>
221
+ <div class="bg-purple-50 rounded-lg p-2 sm:p-3">
222
+ <p class="text-xs sm:text-sm text-purple-600">Total Score</p>
223
+ <p id="final-score" class="font-bold text-lg sm:text-xl">750</p>
224
+ </div>
225
+ </div>
226
+ <button id="play-again" class="w-full bg-purple-600 hover:bg-purple-700 text-white py-2 sm:py-3 rounded-lg shadow-lg font-bold text-sm sm:text-base">
227
+ Play Again <i class="fas fa-redo ml-1 sm:ml-2"></i>
228
+ </button>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ </div>
233
+
234
+ <!-- Audio elements for sound effects -->
235
+ <audio id="flip-sound" src="https://kexel-git.static.hf.space/gsom/zapsplat_multimedia_button_click.mp3" preload="auto"></audio>
236
+ <audio id="match-sound" src="https://kexel-git.static.hf.space/gsom/positive-beeps.mp3" preload="auto"></audio>
237
+ <audio id="mismatch-sound" src="https://kexel-git.static.hf.space/gsom/mixkit-quick-jump-arcade-game-239.mp3" preload="auto"></audio>
238
+ <audio id="hint-sound" src="https://kexel-git.static.hf.space/gsom/unlock-game-notification.mp3" preload="auto"></audio>
239
+ <audio id="level-complete-sound" src="https://kexel-git.static.hf.space/gsom/2015-preview.mp3" preload="auto"></audio>
240
+ <audio id="game-over-sound" src="https://kexel-git.static.hf.space/gsom/2027-preview.mp3" preload="auto"></audio>
241
+ <audio id="button-click-sound" src="https://kexel-git.static.hf.space/gsom/mixkit-mouse-click-close-1113.mp3" preload="auto"></audio>
242
+
243
+ <script>
244
+ document.addEventListener('DOMContentLoaded', () => {
245
+ // Game state
246
+ const state = {
247
+ board: [],
248
+ level: 1,
249
+ score: 0,
250
+ time: 0,
251
+ timerInterval: null,
252
+ selectedTiles: [],
253
+ matchedPairs: 0,
254
+ totalPairs: 0,
255
+ soundEnabled: true,
256
+ gameActive: false,
257
+ isMobile: window.innerWidth < 640 // Check if mobile device
258
+ };
259
+
260
+ // Audio elements
261
+ const flipSound = document.getElementById('flip-sound');
262
+ const matchSound = document.getElementById('match-sound');
263
+ const mismatchSound = document.getElementById('mismatch-sound');
264
+ const hintSound = document.getElementById('hint-sound');
265
+ const levelCompleteSound = document.getElementById('level-complete-sound');
266
+ const gameOverSound = document.getElementById('game-over-sound');
267
+ const buttonClickSound = document.getElementById('button-click-sound');
268
+
269
+ // DOM elements
270
+ const boardElement = document.getElementById('board');
271
+ const timerElement = document.getElementById('timer');
272
+ const levelElement = document.getElementById('level');
273
+ const scoreElement = document.getElementById('score');
274
+ const newGameButton = document.getElementById('new-game');
275
+ const hintButton = document.getElementById('hint');
276
+ const soundToggleButton = document.getElementById('sound-toggle');
277
+ const gameStatusElement = document.getElementById('game-status');
278
+ const levelCompleteModal = document.getElementById('level-complete-modal');
279
+ const gameOverModal = document.getElementById('game-over-modal');
280
+ const nextLevelButton = document.getElementById('next-level');
281
+ const playAgainButton = document.getElementById('play-again');
282
+ const levelTimeElement = document.getElementById('level-time');
283
+ const levelScoreElement = document.getElementById('level-score');
284
+ const finalLevelElement = document.getElementById('final-level');
285
+ const finalScoreElement = document.getElementById('final-score');
286
+
287
+ // Tile types (Vita Mahjong style)
288
+ const tileTypes = [
289
+ '1m', '2m', '3m', '4m', '5m', '6m', '7m', '8m', '9m', // Characters
290
+ '1s', '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', // Bamboo
291
+ '1p', '2p', '3p', '4p', '5p', '6p', '7p', '8p', '9p', // Circles
292
+ 'ew', 'sw', 'ww', 'nw', // Winds
293
+ 'wd', 'gd', 'rd' // Dragons
294
+ ];
295
+
296
+ // Initialize game
297
+ function initGame() {
298
+ state.level = 1;
299
+ state.score = 0;
300
+ state.time = 0;
301
+ state.matchedPairs = 0;
302
+ state.gameActive = true;
303
+
304
+ clearInterval(state.timerInterval);
305
+ startTimer();
306
+
307
+ updateUI();
308
+ createBoard();
309
+
310
+ // Play button click sound
311
+ playSound('button-click');
312
+ }
313
+
314
+ // Create game board based on current level
315
+ function createBoard() {
316
+ boardElement.innerHTML = '';
317
+ state.board = [];
318
+ state.selectedTiles = [];
319
+ state.matchedPairs = 0;
320
+
321
+ // Determine number of pairs based on level and screen size
322
+ const basePairs = state.isMobile ? 2 : 4;
323
+ const pairs = Math.min(basePairs + state.level, state.isMobile ? 12 : 32);
324
+ state.totalPairs = pairs;
325
+
326
+ // Create array of tile pairs
327
+ let tiles = [];
328
+ const availableTypes = [...tileTypes].sort(() => 0.5 - Math.random()).slice(0, pairs);
329
+
330
+ availableTypes.forEach(type => {
331
+ tiles.push(type, type);
332
+ });
333
+
334
+ // Shuffle tiles
335
+ tiles = shuffleArray(tiles);
336
+
337
+ // Determine grid columns based on screen size
338
+ const cols = state.isMobile ? 4 : (window.innerWidth < 768 ? 6 : 8);
339
+ const rows = Math.ceil(tiles.length / cols);
340
+
341
+ for (let i = 0; i < rows; i++) {
342
+ const row = [];
343
+ for (let j = 0; j < cols; j++) {
344
+ const index = i * cols + j;
345
+ if (index < tiles.length) {
346
+ row.push({
347
+ type: tiles[index],
348
+ flipped: false,
349
+ matched: false,
350
+ row: i,
351
+ col: j
352
+ });
353
+ } else {
354
+ row.push(null); // Empty slot for uneven boards
355
+ }
356
+ }
357
+ state.board.push(row);
358
+ }
359
+
360
+ // Render tiles
361
+ renderBoard();
362
+ }
363
+
364
+ // Render the game board
365
+ function renderBoard() {
366
+ boardElement.innerHTML = '';
367
+
368
+ // Calculate grid columns based on screen size
369
+ const cols = state.isMobile ? 4 : (window.innerWidth < 768 ? 6 : 8);
370
+ boardElement.className = `grid gap-2 sm:gap-3 mx-auto`;
371
+ boardElement.style.gridTemplateColumns = `repeat(${cols}, minmax(0, 1fr))`;
372
+
373
+ state.board.forEach((row, rowIndex) => {
374
+ row.forEach((tile, colIndex) => {
375
+ if (!tile) return;
376
+
377
+ const tileElement = document.createElement('div');
378
+ tileElement.className = `tile aspect-square cursor-pointer transition-all duration-300 ${tile.matched ? 'opacity-0' : ''}`;
379
+
380
+ tileElement.innerHTML = `
381
+ <div class="tile-inner ${tile.flipped ? 'flipped' : ''}">
382
+ <div class="tile-back flex items-center justify-center">
383
+ <i class="fas fa-dragon text-xl sm:text-2xl"></i>
384
+ </div>
385
+ <div class="tile-front">
386
+ <span class="text-xl sm:text-3xl font-bold">${getTileSymbol(tile.type)}</span>
387
+ </div>
388
+ </div>
389
+ `;
390
+
391
+ tileElement.addEventListener('click', () => handleTileClick(tile));
392
+
393
+ if (!tile.matched) {
394
+ tileElement.classList.add('tile-hover');
395
+ }
396
+
397
+ boardElement.appendChild(tileElement);
398
+ });
399
+ });
400
+ }
401
+
402
+ // Handle tile click
403
+ function handleTileClick(tile) {
404
+ if (!state.gameActive || tile.matched || tile.flipped || state.selectedTiles.length >= 2) {
405
+ return;
406
+ }
407
+
408
+ // Flip the tile
409
+ tile.flipped = true;
410
+ state.selectedTiles.push(tile);
411
+
412
+ // Play sound
413
+ playSound('flip');
414
+
415
+ renderBoard();
416
+
417
+ // Check for match if two tiles are selected
418
+ if (state.selectedTiles.length === 2) {
419
+ const [tile1, tile2] = state.selectedTiles;
420
+
421
+ if (tile1.type === tile2.type) {
422
+ // Match found
423
+ tile1.matched = true;
424
+ tile2.matched = true;
425
+ state.matchedPairs++;
426
+ state.score += 50 * state.level;
427
+
428
+ // Play success sound
429
+ playSound('match');
430
+
431
+ // Check if level is complete
432
+ if (state.matchedPairs === state.totalPairs) {
433
+ levelComplete();
434
+ }
435
+
436
+ // Clear selection after delay
437
+ setTimeout(() => {
438
+ state.selectedTiles = [];
439
+ renderBoard();
440
+ }, 500);
441
+ } else {
442
+ // No match
443
+ setTimeout(() => {
444
+ tile1.flipped = false;
445
+ tile2.flipped = false;
446
+ state.selectedTiles = [];
447
+ renderBoard();
448
+
449
+ // Play mismatch sound
450
+ playSound('mismatch');
451
+ }, 1000);
452
+ }
453
+ }
454
+
455
+ updateUI();
456
+ }
457
+
458
+ // Level complete
459
+ function levelComplete() {
460
+ state.gameActive = false;
461
+ clearInterval(state.timerInterval);
462
+
463
+ // Calculate bonus points based on time
464
+ const timeBonus = Math.max(0, 300 - state.time);
465
+ state.score += timeBonus;
466
+
467
+ // Show level complete modal
468
+ levelTimeElement.textContent = formatTime(state.time);
469
+ levelScoreElement.textContent = `+${timeBonus}`;
470
+ levelCompleteModal.classList.remove('hidden');
471
+
472
+ // Play level complete sound
473
+ playSound('level-complete');
474
+
475
+ updateUI();
476
+ }
477
+
478
+ // Next level
479
+ function nextLevel() {
480
+ playSound('button-click');
481
+
482
+ state.level++;
483
+ state.time = 0;
484
+ state.gameActive = true;
485
+
486
+ levelCompleteModal.classList.add('hidden');
487
+ startTimer();
488
+ createBoard();
489
+ updateUI();
490
+
491
+ // Show level up message
492
+ gameStatusElement.classList.remove('hidden');
493
+ gameStatusElement.querySelector('p').textContent = `Level ${state.level}!`;
494
+
495
+ setTimeout(() => {
496
+ gameStatusElement.classList.add('hidden');
497
+ }, 2000);
498
+ }
499
+
500
+ // Game over
501
+ function gameOver() {
502
+ state.gameActive = false;
503
+ clearInterval(state.timerInterval);
504
+
505
+ // Update final stats
506
+ finalLevelElement.textContent = state.level;
507
+ finalScoreElement.textContent = state.score;
508
+
509
+ // Show game over modal
510
+ gameOverModal.classList.remove('hidden');
511
+
512
+ // Play game over sound
513
+ playSound('game-over');
514
+ }
515
+
516
+ // Start timer
517
+ function startTimer() {
518
+ state.time = 0;
519
+ updateTimerDisplay();
520
+
521
+ state.timerInterval = setInterval(() => {
522
+ state.time++;
523
+ updateTimerDisplay();
524
+
525
+ // Game over if time exceeds limit (5 minutes)
526
+ if (state.time >= 300) {
527
+ gameOver();
528
+ }
529
+ }, 1000);
530
+ }
531
+
532
+ // Update timer display
533
+ function updateTimerDisplay() {
534
+ timerElement.textContent = formatTime(state.time);
535
+ }
536
+
537
+ // Format time as MM:SS
538
+ function formatTime(seconds) {
539
+ const mins = Math.floor(seconds / 60).toString().padStart(2, '0');
540
+ const secs = (seconds % 60).toString().padStart(2, '0');
541
+ return `${mins}:${secs}`;
542
+ }
543
+
544
+ // Update UI elements
545
+ function updateUI() {
546
+ levelElement.textContent = `Level ${state.level}`;
547
+ scoreElement.textContent = state.score;
548
+ }
549
+
550
+ // Get tile symbol for display
551
+ function getTileSymbol(type) {
552
+ const symbols = {
553
+ '1m': '一', '2m': '二', '3m': '三', '4m': '四', '5m': '五',
554
+ '6m': '六', '7m': '七', '8m': '八', '9m': '九',
555
+ '1s': '1', '2s': '2', '3s': '3', '4s': '4', '5s': '5',
556
+ '6s': '6', '7s': '7', '8s': '8', '9s': '9',
557
+ '1p': '❶', '2p': '❷', '3p': '❸', '4p': '❹', '5p': '❺',
558
+ '6p': '❻', '7p': '❼', '8p': '❽', '9p': '❾',
559
+ 'ew': '東', 'sw': '南', 'ww': '西', 'nw': '北',
560
+ 'wd': '白', 'gd': '發', 'rd': '中'
561
+ };
562
+ return symbols[type] || type;
563
+ }
564
+
565
+ // Shuffle array
566
+ function shuffleArray(array) {
567
+ const newArray = [...array];
568
+ for (let i = newArray.length - 1; i > 0; i--) {
569
+ const j = Math.floor(Math.random() * (i + 1));
570
+ [newArray[i], newArray[j]] = [newArray[j], newArray[i]];
571
+ }
572
+ return newArray;
573
+ }
574
+
575
+ // Play sound
576
+ function playSound(type) {
577
+ if (!state.soundEnabled) return;
578
+
579
+ try {
580
+ switch (type) {
581
+ case 'flip':
582
+ flipSound.currentTime = 0;
583
+ flipSound.play();
584
+ break;
585
+ case 'match':
586
+ matchSound.currentTime = 0;
587
+ matchSound.play();
588
+ break;
589
+ case 'mismatch':
590
+ mismatchSound.currentTime = 0;
591
+ mismatchSound.play();
592
+ break;
593
+ case 'hint':
594
+ hintSound.currentTime = 0;
595
+ hintSound.play();
596
+ break;
597
+ case 'level-complete':
598
+ levelCompleteSound.currentTime = 0;
599
+ levelCompleteSound.play();
600
+ break;
601
+ case 'game-over':
602
+ gameOverSound.currentTime = 0;
603
+ gameOverSound.play();
604
+ break;
605
+ case 'button-click':
606
+ buttonClickSound.currentTime = 0;
607
+ buttonClickSound.play();
608
+ break;
609
+ }
610
+ } catch (e) {
611
+ console.error('Error playing sound:', e);
612
+ }
613
+ }
614
+
615
+ // Provide hint
616
+ function provideHint() {
617
+ if (!state.gameActive || state.matchedPairs === state.totalPairs) {
618
+ return;
619
+ }
620
+
621
+ // Play button click sound
622
+ playSound('button-click');
623
+
624
+ // Find all unflipped, unmatched tiles
625
+ const unflippedTiles = [];
626
+ state.board.forEach(row => {
627
+ row.forEach(tile => {
628
+ if (tile && !tile.flipped && !tile.matched) {
629
+ unflippedTiles.push(tile);
630
+ }
631
+ });
632
+ });
633
+
634
+ if (unflippedTiles.length < 2) return;
635
+
636
+ // Find a matching pair
637
+ const tileCount = {};
638
+ let hintTile1 = null;
639
+ let hintTile2 = null;
640
+
641
+ for (const tile of unflippedTiles) {
642
+ if (tileCount[tile.type]) {
643
+ hintTile1 = tileCount[tile.type];
644
+ hintTile2 = tile;
645
+ break;
646
+ }
647
+ tileCount[tile.type] = tile;
648
+ }
649
+
650
+ if (hintTile1 && hintTile2) {
651
+ // Highlight the hint tiles
652
+ const tileElements = boardElement.querySelectorAll('.tile');
653
+
654
+ tileElements.forEach((element, index) => {
655
+ const cols = state.isMobile ? 4 : (window.innerWidth < 768 ? 6 : 8);
656
+ const row = Math.floor(index / cols);
657
+ const col = index % cols;
658
+ const tile = state.board[row]?.[col];
659
+
660
+ if (tile === hintTile1 || tile === hintTile2) {
661
+ element.classList.add('ring-4', 'ring-yellow-400', 'ring-opacity-75');
662
+
663
+ // Remove highlight after delay
664
+ setTimeout(() => {
665
+ element.classList.remove('ring-4', 'ring-yellow-400', 'ring-opacity-75');
666
+ }, 2000);
667
+ }
668
+ });
669
+
670
+ // Deduct points for using hint
671
+ state.score = Math.max(0, state.score - 25);
672
+ updateUI();
673
+
674
+ // Play hint sound
675
+ playSound('hint');
676
+ }
677
+ }
678
+
679
+ // Event listeners
680
+ newGameButton.addEventListener('click', initGame);
681
+ hintButton.addEventListener('click', provideHint);
682
+ soundToggleButton.addEventListener('click', () => {
683
+ state.soundEnabled = !state.soundEnabled;
684
+ soundToggleButton.innerHTML = state.soundEnabled
685
+ ? '<i class="fas fa-volume-up mr-1 sm:mr-2"></i> Sound On'
686
+ : '<i class="fas fa-volume-mute mr-1 sm:mr-2"></i> Sound Off';
687
+ playSound('button-click');
688
+ });
689
+ nextLevelButton.addEventListener('click', nextLevel);
690
+ playAgainButton.addEventListener('click', () => {
691
+ playSound('button-click');
692
+ gameOverModal.classList.add('hidden');
693
+ initGame();
694
+ });
695
+
696
+ // Handle window resize
697
+ window.addEventListener('resize', () => {
698
+ state.isMobile = window.innerWidth < 640;
699
+ if (state.gameActive) {
700
+ createBoard();
701
+ }
702
+ });
703
+
704
+ // Initialize the game
705
+ initGame();
706
+ });
707
+ </script>
708
+ </body>
709
+ </html>
710
+
711
+
712
+ <!--<!DOCTYPE html>
713
+ <html lang="en">
714
+ <head>
715
+ <meta charset="UTF-8">
716
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
717
+ <title>Vita Mahjong</title>
718
+ <script src="https://cdn.tailwindcss.com"></script>
719
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
720
+ <style>
721
+ @keyframes tileHover {
722
+ 0% { transform: translateY(0); }
723
+ 50% { transform: translateY(-5px); }
724
+ 100% { transform: translateY(0); }
725
+ }
726
+
727
+ .tile-hover:hover {
728
+ animation: tileHover 0.3s ease;
729
+ transform: translateY(-5px);
730
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.2);
731
+ }
732
+
733
+ .tile-selected {
734
+ transform: translateY(-15px);
735
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
736
+ z-index: 10;
737
+ }
738
+
739
+ .tile-matched {
740
+ animation: fadeOut 0.5s ease forwards;
741
+ }
742
+
743
+ @keyframes fadeOut {
744
+ to {
745
+ opacity: 0;
746
+ transform: scale(0.8);
747
+ }
748
+ }
749
+
750
+ .board-container {
751
+ perspective: 1000px;
752
+ }
753
+
754
+ .tile {
755
+ transition: all 0.3s ease;
756
+ transform-style: preserve-3d;
757
+ }
758
+
759
+ .tile-inner {
760
+ position: relative;
761
+ width: 100%;
762
+ height: 100%;
763
+ transform-style: preserve-3d;
764
+ }
765
+
766
+ .tile-front, .tile-back {
767
+ position: absolute;
768
+ width: 100%;
769
+ height: 100%;
770
+ backface-visibility: hidden;
771
+ border-radius: 8px;
772
+ display: flex;
773
+ align-items: center;
774
+ justify-content: center;
775
+ }
776
+
777
+ .tile-front {
778
+ background: linear-gradient(145deg, #f0f0f0, #ffffff);
779
+ transform: rotateY(180deg);
780
+ }
781
+
782
+ .tile-back {
783
+ background: linear-gradient(145deg, #4f46e5, #7c3aed);
784
+ color: white;
785
+ }
786
+
787
+ .flipped {
788
+ transform: rotateY(180deg);
789
+ }
790
+
791
+ .level-complete {
792
+ animation: pulse 2s infinite;
793
+ }
794
+
795
+ @keyframes pulse {
796
+ 0% { transform: scale(1); }
797
+ 50% { transform: scale(1.05); }
798
+ 100% { transform: scale(1); }
799
+ }
800
  </style>
801
  </head>
802
  <body class="bg-gray-100 min-h-screen font-sans">
803
  <div class="container mx-auto px-4 py-8">
804
+
805
  <header class="flex justify-between items-center mb-8">
806
  <div class="flex items-center">
807
  <i class="fas fa-dragon text-4xl text-purple-600 mr-3"></i>
 
823
  </div>
824
  </header>
825
 
826
+
827
  <div class="flex justify-between mb-6">
828
  <div class="flex space-x-3">
829
  <button id="new-game" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg shadow flex items-center">
 
840
  </div>
841
  </div>
842
 
843
+
844
  <div class="board-container bg-white rounded-xl shadow-xl p-6 mb-6">
845
  <div id="board" class="grid grid-cols-8 gap-3 mx-auto"></div>
846
  </div>
847
 
848
+
849
  <div id="game-status" class="text-center mb-6 hidden">
850
  <div class="inline-block bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg">
851
  <p class="font-bold">Level Complete!</p>
852
  </div>
853
  </div>
854
 
855
+
856
  <div id="level-complete-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
857
  <div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full level-complete">
858
  <div class="text-center">
 
878
  </div>
879
  </div>
880
 
881
+
882
  <div id="game-over-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
883
  <div class="bg-white rounded-xl shadow-2xl p-8 max-w-md w-full">
884
  <div class="text-center">
 
905
  </div>
906
  </div>
907
 
908
+
909
  <audio id="flip-sound" src="https://kexel-git.static.hf.space/gsom/zapsplat_multimedia_button_click.mp3" preload="auto"></audio>
910
  <audio id="match-sound" src="https://kexel-git.static.hf.space/gsom/positive-beeps.mp3" preload="auto"></audio>
911
  <audio id="mismatch-sound" src="https://kexel-git.static.hf.space/gsom/mixkit-quick-jump-arcade-game-239.mp3" preload="auto"></audio>
 
1370
  </script>
1371
  </body>
1372
  </html>
1373
+ -->
1374
 
1375
 
1376
  <!--<!DOCTYPE html>