diff --git a/index.html b/index.html index 8e546ee..4648ed2 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,10 @@
+
+

Next

+ +

Score: 0

Level: 1

Lines: 0

@@ -27,10 +31,6 @@
-
-

Next

- -
diff --git a/script.js b/script.js index 7055956..6d0484f 100644 --- a/script.js +++ b/script.js @@ -13,6 +13,10 @@ const PREVIEW_BLOCK_SIZE = 25; const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); let touchControls = false; +// 7-bag randomization variables +let pieceBag = []; +let nextBag = []; + // Set canvas dimensions to match game board canvas.width = COLS * BLOCK_SIZE; canvas.height = ROWS * BLOCK_SIZE; @@ -1510,11 +1514,13 @@ function drawBoard() { // Generate random piece function randomPiece() { - let randomN = Math.floor(Math.random() * PIECES.length); - let randomTetromino = PIECES[randomN]; - let randomIndex = Math.floor(Math.random() * randomTetromino.length); + // Initialize bags if they're empty + if (pieceBag.length === 0 && nextBag.length === 0) { + pieceBag = generateBag(); + nextBag = generateBag(); + } - return new Piece(randomTetromino, randomIndex, COLORS[randomN]); + return getNextPieceFromBag(); } // Play piece movement sounds @@ -1844,6 +1850,7 @@ function init() { function handleResize() { const gameContainer = document.querySelector('.game-container'); const gameWrapper = document.querySelector('.game-wrapper'); + const scoreContainer = document.querySelector('.score-container'); if (isMobile || forceMobileControls) { // Scale the canvas to fit mobile screen @@ -1855,42 +1862,42 @@ function handleResize() { // Calculate available game area (accounting for UI elements) const titleHeight = 40; // Estimate for title - const scoreHeight = isPortrait ? 120 : 0; // In portrait, score is above/below game - const availableHeight = viewportHeight - titleHeight - scoreHeight; + const scoreWidth = isPortrait ? 120 : 100; // Width for score container in portrait/landscape + const availableWidth = viewportWidth - scoreWidth - 20; // Subtract score width + padding + const availableHeight = viewportHeight - titleHeight - 20; // Subtract title height + padding + + // Calculate optimal dimensions while maintaining aspect ratio + const gameRatio = ROWS / COLS; + + // Calculate scale based on available space + let targetWidth, targetHeight; - // For portrait: maximize width, maintain aspect ratio if (isPortrait) { - // Use 90% of viewport width - const targetWidth = viewportWidth * 0.9; - const targetHeight = (targetWidth / COLS) * ROWS; + // For portrait, prioritize fitting the width + targetWidth = availableWidth * 0.95; + targetHeight = targetWidth * gameRatio; - // If height is too tall, scale down - if (targetHeight > availableHeight * 0.8) { - const scaleFactor = (availableHeight * 0.8) / targetHeight; - canvas.style.width = `${targetWidth * scaleFactor}px`; - canvas.style.height = `${targetHeight * scaleFactor}px`; - } else { - canvas.style.width = `${targetWidth}px`; - canvas.style.height = `${targetHeight}px`; + // If too tall, scale down based on height + if (targetHeight > availableHeight * 0.95) { + targetHeight = availableHeight * 0.95; + targetWidth = targetHeight / gameRatio; } - } - // For landscape: maximize height, maintain aspect ratio - else { - // Use 75% of available height - const targetHeight = availableHeight * 0.75; - const targetWidth = (targetHeight / ROWS) * COLS; + } else { + // For landscape, prioritize fitting the height + targetHeight = availableHeight * 0.95; + targetWidth = targetHeight / gameRatio; - // If width is too wide, scale down - if (targetWidth > viewportWidth * 0.6) { - const scaleFactor = (viewportWidth * 0.6) / targetWidth; - canvas.style.width = `${targetWidth * scaleFactor}px`; - canvas.style.height = `${targetHeight * scaleFactor}px`; - } else { - canvas.style.width = `${targetWidth}px`; - canvas.style.height = `${targetHeight}px`; + // If too wide, scale down based on width + if (targetWidth > availableWidth * 0.95) { + targetWidth = availableWidth * 0.95; + targetHeight = targetWidth * gameRatio; } } + // Apply dimensions + canvas.style.width = `${targetWidth}px`; + canvas.style.height = `${targetHeight}px`; + // Force a redraw of the nextPiece preview to fix rendering issues if (nextPiece) { setTimeout(() => { @@ -1906,10 +1913,6 @@ function handleResize() { canvas.style.width = ''; canvas.style.height = ''; - if (nextPieceCanvas) { - nextPieceCanvas.style.transform = 'none'; - } - document.body.classList.remove('mobile-mode'); } } @@ -2386,6 +2389,40 @@ function lockPiece() { playSound(dropSound); } +// Generate pieces using 7-bag randomization +function generateBag() { + // Create array with indices 0-6 (one for each piece type) + let bag = [0, 1, 2, 3, 4, 5, 6]; + + // Fisher-Yates shuffle algorithm + for (let i = bag.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [bag[i], bag[j]] = [bag[j], bag[i]]; // Swap elements + } + + return bag; +} + +// Get next piece from the bag +function getNextPieceFromBag() { + // If the current bag is empty, use the prepared next bag + if (pieceBag.length === 0) { + pieceBag = nextBag.slice(); + // Generate new bag for next time + nextBag = generateBag(); + } + + // Take the first piece from the bag + const pieceIndex = pieceBag.shift(); + const tetromino = PIECES[pieceIndex]; + const color = COLORS[pieceIndex]; + + // Random rotation/orientation + const randomIndex = Math.floor(Math.random() * tetromino.length); + + return new Piece(tetromino, randomIndex, color); +} + // Start the game window.onload = function() { init(); diff --git a/style.css b/style.css index d621991..fd726cb 100644 --- a/style.css +++ b/style.css @@ -496,18 +496,18 @@ input[type=range]::-moz-range-thumb { } .mobile-mode .game-container { - flex-direction: column; - align-items: center; - gap: 15px; + flex-direction: row; + align-items: flex-start; + gap: 5px; padding: 10px; max-width: 100vw; box-sizing: border-box; - margin-top: 50px; /* Reduced top margin */ + margin-top: 40px; /* Reduced top margin */ } .mobile-mode .game-title { - font-size: 24px; - top: 10px; + font-size: 22px; + top: 5px; text-shadow: 0 0 8px rgba(255, 0, 255, 0.7); } @@ -515,50 +515,142 @@ input[type=range]::-moz-range-thumb { display: flex; flex-direction: column; align-items: center; - margin: 0 auto; - position: relative; - margin-bottom: 20px; /* Add bottom margin to make room for next piece */ -} - -.mobile-mode #next-piece-preview { - position: absolute; - top: auto; - bottom: -70px; /* Position below the game board */ - left: 50%; - transform: translateX(-50%); margin: 0; - background: rgba(0, 0, 0, 0.7); - z-index: 10; - border-color: rgba(0, 255, 255, 0.5); + order: 2; /* Move game board to the right */ + flex-grow: 1; } .mobile-mode .score-container { - width: 100%; + width: auto; + min-width: 100px; + max-width: 120px; display: flex; - flex-direction: row; - flex-wrap: wrap; - justify-content: center; + flex-direction: column; align-items: center; - gap: 8px; + gap: 5px; padding: 8px; background: rgba(0, 0, 0, 0.7); + order: 1; /* Move score container to the left */ + margin-right: 5px; + border-radius: 8px; + height: auto; + align-self: stretch; } .mobile-mode .score-container p { - margin: 5px; - font-size: 12px; + margin: 3px 0; + font-size: 10px; } .mobile-mode .game-btn { margin: 3px; - padding: 8px 12px; - font-size: 10px; + padding: 6px 8px; + font-size: 9px; + width: 95%; } .mobile-mode .controls-info { display: none; } +.mobile-mode #next-piece-preview { + position: relative; + top: auto; + bottom: auto; + left: auto; + right: auto; + transform: none; + margin: 0 0 10px 0; + background: rgba(0, 0, 0, 0.7); + z-index: 10; + border-color: rgba(0, 255, 255, 0.5); + width: 90%; /* Make it fit inside the score container */ + box-sizing: border-box; +} + +.mobile-mode #next-piece-preview h3 { + font-size: 12px; + margin-bottom: 5px; +} + +.mobile-mode canvas#tetris { + max-width: 100%; + height: auto; + border-radius: 8px; +} + +/* Portrait vs landscape adjustments */ +@media (orientation: portrait) { + .mobile-mode .game-container { + padding-top: 35px; + height: calc(100vh - 40px); + } + + .mobile-mode .score-container { + height: calc(100vh - 80px); + justify-content: flex-start; + } + + .mobile-mode canvas#tetris { + height: calc(100vh - 80px); + width: auto; + object-fit: contain; + } +} + +@media (orientation: landscape) { + .mobile-mode .game-container { + margin-top: 35px; + padding: 5px; + height: calc(100vh - 40px); + } + + .mobile-mode .score-container { + height: calc(100vh - 50px); + max-width: 100px; + } + + .mobile-mode canvas#tetris { + height: calc(100vh - 50px); + width: auto; + object-fit: contain; + } +} + +/* Smaller devices */ +@media (max-width: 400px) { + .mobile-mode .game-title { + font-size: 18px; + } + + .mobile-mode .score-container p { + font-size: 9px; + } + + .mobile-mode .score-container { + min-width: 85px; + max-width: 90px; + } + + .mobile-mode .game-btn { + font-size: 8px; + padding: 5px 6px; + } +} + +/* Tablet optimization */ +@media (min-width: 768px) and (max-width: 1024px) { + .mobile-mode .game-container { + flex-direction: row; + flex-wrap: wrap; + gap: 20px; + } + + .mobile-mode .game-title { + font-size: 28px; + } +} + /* Touch instructions */ .touch-instructions { display: none; @@ -588,81 +680,4 @@ input[type=range]::-moz-range-thumb { .touch-instructions.fade-out { opacity: 0; -} - -/* Portrait vs landscape adjustments */ -@media (orientation: portrait) { - .mobile-mode .game-container { - padding-top: 40px; - padding-bottom: 80px; /* Add bottom padding for next piece */ - } - - .mobile-mode canvas#tetris { - width: 90vw; - max-height: 75vh; /* Ensure game board doesn't take too much height */ - height: auto; - object-fit: contain; - } -} - -@media (orientation: landscape) { - .mobile-mode .game-container { - flex-direction: row; - flex-wrap: wrap; - justify-content: center; - margin-top: 40px; - padding: 5px; - } - - .mobile-mode .score-container { - width: auto; - min-width: 150px; - max-width: 30%; - height: 80vh; - overflow-y: auto; - } - - .mobile-mode .game-wrapper { - margin: 0; - height: 80vh; - } - - .mobile-mode #next-piece-preview { - position: absolute; - top: 0; - bottom: auto; - left: 100%; - transform: none; - margin-left: 10px; - } - - .mobile-mode canvas#tetris { - height: 75vh; /* Slightly reduced to ensure full visibility */ - width: auto; - object-fit: contain; - } -} - -/* Smaller devices */ -@media (max-width: 400px) { - .mobile-mode .game-title { - font-size: 20px; - } - - .mobile-mode .score-container p { - font-size: 10px; - } -} - -/* Tablet optimization */ -@media (min-width: 768px) and (max-width: 1024px) { - .mobile-mode .game-container { - flex-direction: row; - flex-wrap: wrap; - gap: 20px; - } - - .mobile-mode .game-title { - font-size: 28px; - } } \ No newline at end of file