Guest User

lithiumflow Tetris

a guest
Oct 19th, 2025
972
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 23.31 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Glass Tetris</title>
  7. <style>
  8. @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap');
  9.  
  10. :root {
  11. --bg-glass: rgba(255, 255, 255, 0.1);
  12. --shadow-glass: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
  13. --border-glass: 1px solid rgba(255, 255, 255, 0.18);
  14. --text-color: #ffffff;
  15. }
  16.  
  17. * {
  18. box-sizing: border-box;
  19. margin: 0;
  20. padding: 0;
  21. }
  22.  
  23. body {
  24. min-height: 100vh;
  25. display: flex;
  26. justify-content: center;
  27. align-items: center;
  28. font-family: 'Poppins', sans-serif;
  29. background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
  30. background-size: 400% 400%;
  31. animation: gradientBG 15s ease infinite;
  32. color: var(--text-color);
  33. overflow: hidden;
  34. }
  35.  
  36. @keyframes gradientBG {
  37. 0% { background-position: 0% 50%; }
  38. 50% { background-position: 100% 50%; }
  39. 100% { background-position: 0% 50%; }
  40. }
  41.  
  42. /* Decorative background orbs to enhance glass effect */
  43. .orb {
  44. position: absolute;
  45. border-radius: 50%;
  46. filter: blur(80px);
  47. opacity: 0.4;
  48. z-index: -1;
  49. }
  50. .orb-1 {
  51. top: 10%; left: 20%; width: 300px; height: 300px; background: #ff00cc;
  52. }
  53. .orb-2 {
  54. bottom: 10%; right: 20%; width: 400px; height: 400px; background: #3333ff;
  55. }
  56.  
  57. .game-container {
  58. display: flex;
  59. gap: 20px;
  60. padding: 30px;
  61. background: var(--bg-glass);
  62. backdrop-filter: blur(16px);
  63. -webkit-backdrop-filter: blur(16px);
  64. border-radius: 20px;
  65. border: var(--border-glass);
  66. box-shadow: var(--shadow-glass);
  67. }
  68.  
  69. .main-board-container {
  70. position: relative;
  71. border-radius: 10px;
  72. overflow: hidden;
  73. background: rgba(0, 0, 0, 0.2);
  74. border: var(--border-glass);
  75. }
  76.  
  77. #tetris {
  78. display: block;
  79. /* The canvas itself is transparent to let background show through */
  80. }
  81.  
  82. .sidebar {
  83. display: flex;
  84. flex-direction: column;
  85. gap: 20px;
  86. min-width: 150px;
  87. }
  88.  
  89. .panel {
  90. padding: 15px;
  91. background: var(--bg-glass);
  92. border-radius: 15px;
  93. border: var(--border-glass);
  94. text-align: center;
  95. }
  96.  
  97. .panel h2 {
  98. font-size: 1.2rem;
  99. font-weight: 300;
  100. opacity: 0.8;
  101. margin-bottom: 5px;
  102. }
  103.  
  104. .panel .value {
  105. font-size: 1.8rem;
  106. font-weight: 600;
  107. text-shadow: 0 0 10px rgba(255,255,255,0.3);
  108. }
  109.  
  110. #next-piece {
  111. margin: 0 auto;
  112. display: block;
  113. margin-top: 10px;
  114. }
  115.  
  116. .controls-info {
  117. font-size: 0.8rem;
  118. opacity: 0.6;
  119. text-align: left;
  120. margin-top: auto;
  121. }
  122.  
  123. .overlay {
  124. position: absolute;
  125. top: 0; left: 0; right: 0; bottom: 0;
  126. background: rgba(0,0,0,0.6);
  127. backdrop-filter: blur(4px);
  128. display: flex;
  129. flex-direction: column;
  130. justify-content: center;
  131. align-items: center;
  132. z-index: 10;
  133. }
  134.  
  135. .overlay h1 {
  136. font-size: 3rem;
  137. margin-bottom: 20px;
  138. color: white;
  139. text-shadow: 0 0 20px rgba(255,255,255,0.5);
  140. }
  141.  
  142. .btn {
  143. padding: 12px 30px;
  144. font-size: 1.2rem;
  145. border: none;
  146. background: rgba(255, 255, 255, 0.2);
  147. color: white;
  148. border-radius: 50px;
  149. cursor: pointer;
  150. transition: all 0.3s ease;
  151. border: 1px solid rgba(255,255,255,0.4);
  152. font-family: inherit;
  153. }
  154.  
  155. .btn:hover {
  156. background: rgba(255, 255, 255, 0.4);
  157. box-shadow: 0 0 15px rgba(255,255,255,0.4);
  158. }
  159.  
  160. .hidden {
  161. display: none !important;
  162. }
  163.  
  164. </style>
  165. </head>
  166. <body>
  167.  
  168. <!-- Background Orbs for visual depth -->
  169. <div class="orb orb-1"></div>
  170. <div class="orb orb-2"></div>
  171.  
  172. <div class="game-container">
  173. <!-- Main Game Area -->
  174. <div class="main-board-container">
  175. <canvas id="tetris" width="300" height="600"></canvas>
  176.  
  177. <!-- Start/Pause/Game Over Overlay -->
  178. <div id="overlay" class="overlay">
  179. <h1 id="overlay-title">Glass Tetris</h1>
  180. <button id="start-btn" class="btn">Start Game</button>
  181. </div>
  182. </div>
  183.  
  184. <!-- Sidebar for stats -->
  185. <div class="sidebar">
  186. <div class="panel">
  187. <h2>Score</h2>
  188. <div id="score" class="value">0</div>
  189. </div>
  190. <div class="panel">
  191. <h2>Level</h2>
  192. <div id="level" class="value">1</div>
  193. </div>
  194. <div class="panel">
  195. <h2>Next</h2>
  196. <canvas id="next-piece" width="80" height="80"></canvas>
  197. </div>
  198.  
  199. <div class="panel controls-info">
  200. <h3>Controls</h3>
  201. <p>← → : Move</p>
  202. <p>↑ : Rotate</p>
  203. <p>↓ : Soft Drop</p>
  204. <p>Space : Hard Drop</p>
  205. <p>P : Pause</p>
  206. </div>
  207. </div>
  208. </div>
  209.  
  210. <script>
  211. const canvas = document.getElementById('tetris');
  212. const ctx = canvas.getContext('2d');
  213. const nextCanvas = document.getElementById('next-piece');
  214. const nextCtx = nextCanvas.getContext('2d');
  215.  
  216. const scoreElement = document.getElementById('score');
  217. const levelElement = document.getElementById('level');
  218. const overlay = document.getElementById('overlay');
  219. const overlayTitle = document.getElementById('overlay-title');
  220. const startBtn = document.getElementById('start-btn');
  221.  
  222. const ROW = 20;
  223. const COL = 10;
  224. const SQ = 30; // Square size
  225. const VACANT = "TRANSPARENT"; // Empty square color
  226.  
  227. let board = [];
  228. let score = 0;
  229. let level = 1;
  230. let gameOver = false;
  231. let isPaused = false;
  232. let dropStart = Date.now();
  233. let dropSpeed = 1000; // initial drop speed in ms
  234.  
  235. // The Tetrominoes and their colors (using RGB for easier alpha manipulation)
  236. const PIECES = [
  237. [Z, "rgb(239, 68, 68)"], // Red
  238. [S, "rgb(34, 197, 94)"], // Green
  239. [T, "rgb(168, 85, 247)"], // Purple
  240. [O, "rgb(234, 179, 8)"], // Yellow
  241. [L, "rgb(249, 115, 22)"], // Orange
  242. [I, "rgb(6, 182, 212)"], // Cyan
  243. [J, "rgb(59, 130, 246)"] // Blue
  244. ];
  245.  
  246. // Shape Definitions
  247. function Z() { return [[[1,1,0],[0,1,1],[0,0,0]], [[0,0,1],[0,1,1],[0,1,0]], [[0,0,0],[1,1,0],[0,1,1]], [[0,1,0],[1,1,0],[1,0,0]]]; }
  248. function S() { return [[[0,1,1],[1,1,0],[0,0,0]], [[0,1,0],[0,1,1],[0,0,1]], [[0,0,0],[0,1,1],[1,1,0]], [[1,0,0],[1,1,0],[0,1,0]]]; }
  249. function T() { return [[[0,1,0],[1,1,1],[0,0,0]], [[0,1,0],[0,1,1],[0,1,0]], [[0,0,0],[1,1,1],[0,1,0]], [[0,1,0],[1,1,0],[0,1,0]]]; }
  250. function O() { return [[[0,0,0,0],[0,1,1,0],[0,1,1,0],[0,0,0,0]]]; } // O only has one state effectively, but kept 4x4 for consistency if needed, simplified here to avoid rotation issues. Actually O doesn't rotate.
  251. function L() { return [[[0,0,1],[1,1,1],[0,0,0]], [[0,1,0],[0,1,0],[0,1,1]], [[0,0,0],[1,1,1],[1,0,0]], [[1,1,0],[0,1,0],[0,1,0]]]; }
  252. function I() { return [[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], [[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]], [[0,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,0,0]], [[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]]]; }
  253. function J() { return [[[1,0,0],[1,1,1],[0,0,0]], [[0,1,1],[0,1,0],[0,1,0]], [[0,0,0],[1,1,1],[0,0,1]], [[0,1,0],[0,1,0],[1,1,0]]]; }
  254.  
  255. // Fix O shape rotation by giving it identical states if we use the standard rotation logic,
  256. // or just handle it separately. Simplest is to just give it 1 state and not rotate it.
  257. // Redefining O for simplicity in uniform rotation calls:
  258. const O_Piece = [[[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]]];
  259.  
  260. // Init Board
  261. function initBoard() {
  262. for (let r = 0; r < ROW; r++) {
  263. board[r] = [];
  264. for (let c = 0; c < COL; c++) {
  265. board[r][c] = VACANT;
  266. }
  267. }
  268. }
  269.  
  270. // Draw a single glass square
  271. function drawSquare(x, y, color, context = ctx, size = SQ) {
  272. if (color === VACANT) {
  273. // We don't draw vacant squares to keep transparency
  274. return;
  275. }
  276.  
  277. const px = x * size;
  278. const py = y * size;
  279.  
  280. // 1. Base semi-transparent fill
  281. // Convert rgb to rgba for transparency if it's not already VACANT
  282. let baseColor = color.replace('rgb', 'rgba').replace(')', ', 0.5)');
  283. context.fillStyle = baseColor;
  284. context.clearRect(px, py, size, size); // Clear first to avoid alpha stacking
  285. context.fillRect(px, py, size, size);
  286.  
  287. // 2. Inner gradient for "glass" Shine
  288. let gradient = context.createLinearGradient(px, py, px + size/2, py + size/2);
  289. gradient.addColorStop(0, "rgba(255, 255, 255, 0.5)");
  290. gradient.addColorStop(1, "rgba(255, 255, 255, 0)");
  291. context.fillStyle = gradient;
  292. context.fillRect(px, py, size, size);
  293.  
  294. // 3. Glass borders (bevel effect)
  295. context.lineWidth = 1;
  296. // Top & Left (Highlight)
  297. context.beginPath();
  298. context.moveTo(px, py + size);
  299. context.lineTo(px, py);
  300. context.lineTo(px + size, py);
  301. context.strokeStyle = "rgba(255, 255, 255, 0.6)";
  302. context.stroke();
  303.  
  304. // Bottom & Right (Shadow)
  305. context.beginPath();
  306. context.moveTo(px + size, py);
  307. context.lineTo(px + size, py + size);
  308. context.lineTo(px, py + size);
  309. context.strokeStyle = "rgba(0, 0, 0, 0.2)";
  310. context.stroke();
  311. }
  312.  
  313. // Draw Board
  314. function drawBoard() {
  315. ctx.clearRect(0, 0, canvas.width, canvas.height);
  316. for (let r = 0; r < ROW; r++) {
  317. for (let c = 0; c < COL; c++) {
  318. drawSquare(c, r, board[r][c]);
  319. }
  320. }
  321. // Optional: draw faint grid lines
  322. ctx.strokeStyle = "rgba(255,255,255,0.05)";
  323. ctx.lineWidth = 1;
  324. for (let r=0; r<=ROW; r++) {
  325. ctx.beginPath(); ctx.moveTo(0, r*SQ); ctx.lineTo(COL*SQ, r*SQ); ctx.stroke();
  326. }
  327. for (let c=0; c<=COL; c++) {
  328. ctx.beginPath(); ctx.moveTo(c*SQ, 0); ctx.lineTo(c*SQ, ROW*SQ); ctx.stroke();
  329. }
  330. }
  331.  
  332. // Piece Object
  333. class Piece {
  334. constructor(tetromino, color) {
  335. this.tetromino = tetromino;
  336. this.color = color;
  337. this.tetrominoN = 0; // rotation index
  338. this.activeTetromino = this.tetromino[this.tetrominoN];
  339.  
  340. // Center the piece initially
  341. this.x = 3;
  342. this.y = -2; // Start slightly above board
  343. }
  344.  
  345. fill(color) {
  346. for (let r = 0; r < this.activeTetromino.length; r++) {
  347. for (let c = 0; c < this.activeTetromino.length; c++) {
  348. if (this.activeTetromino[r][c]) {
  349. drawSquare(this.x + c, this.y + r, color);
  350. }
  351. }
  352. }
  353. }
  354.  
  355. draw() {
  356. this.fill(this.color);
  357. }
  358.  
  359. unDraw() {
  360. // We don't actually need to undraw explicitly if we redraw the whole board every frame,
  361. // but for optimization in some engines you might. Here we redraw board + piece in loop.
  362. }
  363.  
  364. moveDown() {
  365. if (!this.collision(0, 1, this.activeTetromino)) {
  366. this.y++;
  367. } else {
  368. this.lock();
  369. if(!gameOver) {
  370. spawnNextPiece();
  371. }
  372. }
  373. }
  374.  
  375. moveRight() {
  376. if (!this.collision(1, 0, this.activeTetromino)) {
  377. this.x++;
  378. }
  379. }
  380.  
  381. moveLeft() {
  382. if (!this.collision(-1, 0, this.activeTetromino)) {
  383. this.x--;
  384. }
  385. }
  386.  
  387. rotate() {
  388. let nextPattern = this.tetromino[(this.tetrominoN + 1) % this.tetromino.length];
  389. let kick = 0;
  390.  
  391. // Simple wall kick: if rotation collides, try moving left/right once
  392. if (this.collision(0, 0, nextPattern)) {
  393. if (this.x > COL/2) { // it's on the right side, try kicking left
  394. if (!this.collision(-1, 0, nextPattern)) {
  395. this.x -= 1;
  396. kick = 1;
  397. }
  398. } else { // on left side, try kicking right
  399. if (!this.collision(1, 0, nextPattern)) {
  400. this.x += 1;
  401. kick = 1;
  402. }
  403. }
  404. }
  405.  
  406. if (!this.collision(0, 0, nextPattern)) {
  407. this.tetrominoN = (this.tetrominoN + 1) % this.tetromino.length;
  408. this.activeTetromino = this.tetromino[this.tetrominoN];
  409. }
  410. }
  411.  
  412. hardDrop() {
  413. while (!this.collision(0, 1, this.activeTetromino)) {
  414. this.y++;
  415. }
  416. this.lock();
  417. if(!gameOver) {
  418. spawnNextPiece();
  419. }
  420. }
  421.  
  422. lock() {
  423. for (let r = 0; r < this.activeTetromino.length; r++) {
  424. for (let c = 0; c < this.activeTetromino.length; c++) {
  425. // Skip vacant squares
  426. if (!this.activeTetromino[r][c]) continue;
  427.  
  428. // Game Over if locking above board
  429. if (this.y + r < 0) {
  430. endGame();
  431. return;
  432. }
  433. // Lock piece into board
  434. board[this.y + r][this.x + c] = this.color;
  435. }
  436. }
  437. // Clear lines
  438. let rowsCleared = 0;
  439. for (let r = 0; r < ROW; r++) {
  440. let isRowFull = true;
  441. for (let c = 0; c < COL; c++) {
  442. if (board[r][c] === VACANT) {
  443. isRowFull = false;
  444. break;
  445. }
  446. }
  447. if (isRowFull) {
  448. rowsCleared++;
  449. // Move all rows above down
  450. for (let y = r; y > 0; y--) {
  451. for (let c = 0; c < COL; c++) {
  452. board[y][c] = board[y - 1][c];
  453. }
  454. }
  455. // Top row is now vacant
  456. for (let c = 0; c < COL; c++) {
  457. board[0][c] = VACANT;
  458. }
  459. }
  460. }
  461.  
  462. if (rowsCleared > 0) {
  463. // Classic scoring
  464. const lineScores = [0, 40, 100, 300, 1200];
  465. score += lineScores[rowsCleared] * level;
  466. scoreElement.innerText = score;
  467. // Level up every 10 lines or roughly every 1000 points for simplicity here
  468. // Let's do score based leveling
  469. if (score > level * 1000) {
  470. level++;
  471. levelElement.innerText = level;
  472. dropSpeed = Math.max(100, 1000 - (level - 1) * 100);
  473. }
  474. }
  475. }
  476.  
  477. collision(x, y, piece) {
  478. for (let r = 0; r < piece.length; r++) {
  479. for (let c = 0; c < piece.length; c++) {
  480. // If the square is empty, we skip it
  481. if (!piece[r][c]) {
  482. continue;
  483. }
  484. // Coordinates of the piece after movement
  485. let newX = this.x + c + x;
  486. let newY = this.y + r + y;
  487.  
  488. // Conditions
  489. if (newX < 0 || newX >= COL || newY >= ROW) {
  490. return true;
  491. }
  492. // Skip checking board if y is still above it (negative)
  493. if (newY < 0) {
  494. continue;
  495. }
  496. // Check if there is a locked piece already
  497. if (board[newY][newX] !== VACANT) {
  498. return true;
  499. }
  500. }
  501. }
  502. return false;
  503. }
  504. }
  505.  
  506. // Game State Management
  507. let p, nextP;
  508.  
  509. function randomPiece() {
  510. let r = Math.floor(Math.random() * PIECES.length);
  511. return new Piece(PIECES[r][0](), PIECES[r][1]);
  512. }
  513.  
  514. function spawnNextPiece() {
  515. p = nextP;
  516. nextP = randomPiece();
  517. drawNextPiece();
  518. }
  519.  
  520. function drawNextPiece() {
  521. nextCtx.clearRect(0, 0, nextCanvas.width, nextCanvas.height);
  522. // Center the next piece in the small canvas
  523. // Assume standard 4x4 max size, tiles are smaller here usually to fit
  524. let previewSize = 20;
  525. let shape = nextP.activeTetromino;
  526. // Calculate offset to center
  527. let w = shape[0].length * previewSize;
  528. let h = shape.length * previewSize;
  529. let offX = (nextCanvas.width - w) / 2;
  530. let offY = (nextCanvas.height - h) / 2;
  531.  
  532. for(let r=0; r<shape.length; r++){
  533. for(let c=0; c<shape.length; c++){
  534. if(shape[r][c]){
  535. drawSquare(c, r, nextP.color, nextCtx, previewSize);
  536. // Adjust positions because drawSquare uses grid coordinates * size
  537. // We need to manually offset standard drawSquare for the preview canvas if we want it centered nicely without rewriting drawSquare entirely.
  538. // Actually, let's just use a quick custom draw for preview to handle offsets easily.
  539. let px = offX + c * previewSize;
  540. let py = offY + r * previewSize;
  541. nextCtx.fillStyle = nextP.color.replace('rgb', 'rgba').replace(')', ', 0.8)');
  542. nextCtx.fillRect(px, py, previewSize-1, previewSize-1);
  543. nextCtx.strokeStyle = "rgba(255,255,255,0.5)";
  544. nextCtx.strokeRect(px,py,previewSize-1,previewSize-1);
  545. }
  546. }
  547. }
  548. }
  549.  
  550. // Game Loop
  551. function drop() {
  552. if(gameOver || isPaused) return;
  553.  
  554. let now = Date.now();
  555. let delta = now - dropStart;
  556. if (delta > dropSpeed) {
  557. p.moveDown();
  558. dropStart = Date.now();
  559. }
  560.  
  561. drawBoard();
  562. p.draw();
  563. requestAnimationFrame(drop);
  564. }
  565.  
  566. // Controls
  567. document.addEventListener("keydown", CONTROL);
  568. function CONTROL(event) {
  569. if(gameOver) return;
  570.  
  571. // Pause (P key)
  572. if(event.keyCode == 80) {
  573. togglePause();
  574. return;
  575. }
  576.  
  577. if(isPaused) return;
  578.  
  579. if(event.keyCode == 37) {
  580. p.moveLeft();
  581. dropStart = Date.now(); // slight delay after user move before gravity kicks in again
  582. } else if(event.keyCode == 38) {
  583. p.rotate();
  584. dropStart = Date.now();
  585. } else if(event.keyCode == 39) {
  586. p.moveRight();
  587. dropStart = Date.now();
  588. } else if(event.keyCode == 40) {
  589. p.moveDown();
  590. // Don't reset dropStart here, let them hold it to soft drop fast
  591. } else if(event.keyCode == 32) {
  592. p.hardDrop();
  593. }
  594.  
  595. // Redraw immediately on input for responsiveness
  596. drawBoard();
  597. p.draw();
  598. }
  599.  
  600. function togglePause() {
  601. isPaused = !isPaused;
  602. if(isPaused) {
  603. overlayTitle.innerText = "Paused";
  604. startBtn.innerText = "Resume";
  605. overlay.classList.remove("hidden");
  606. startBtn.onclick = togglePause; // Resume on click
  607. } else {
  608. overlay.classList.add("hidden");
  609. drop(); // Restart loop
  610. }
  611. }
  612.  
  613. function endGame() {
  614. gameOver = true;
  615. overlayTitle.innerText = "Game Over";
  616. startBtn.innerText = "Play Again";
  617. overlay.classList.remove("hidden");
  618. startBtn.onclick = resetGame;
  619. }
  620.  
  621. function resetGame() {
  622. initBoard();
  623. score = 0;
  624. level = 1;
  625. dropSpeed = 1000;
  626. gameOver = false;
  627. isPaused = false;
  628. scoreElement.innerText = score;
  629. levelElement.innerText = level;
  630. overlay.classList.add("hidden");
  631.  
  632. nextP = randomPiece();
  633. spawnNextPiece();
  634. dropStart = Date.now();
  635. drop();
  636. }
  637.  
  638. // Initial Setup
  639. startBtn.addEventListener('click', () => {
  640. if(gameOver || overlayTitle.innerText === "Glass Tetris") {
  641. resetGame();
  642. } else {
  643. togglePause();
  644. }
  645. });
  646.  
  647. initBoard();
  648. drawBoard(); // Draw empty board initially
  649.  
  650. </script>
  651. </body>
  652. </html>
Advertisement
Add Comment
Please, Sign In to add comment