+
+
Next
+
+
Score: 0
Level: 1
Lines: 0
@@ -27,10 +31,6 @@
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