VCHS Computer Science
Computer Science at Valley Catholic High School
Pages
Home
APCSA
APCSP
Ten Commandments
Jump
Tetris F1
Tetris
Timer:
0
Score:
0
Difficulty:
Easy
Medium
Hard
Custom
Grid Size:
10x20
15x30
20x40
Game Info
Press the down arrow to start
Press "P" to pause the game
Use arrows keys to rotate and speed up block
Press "Space" to instant place
Press "R" to restart the game
Source Code
<!-- CSS --> <style> #game-environs { display: inline-block; } #game-info { float: center; text-align: center; } #grid { background-image: url(https://i.imgur.com/7mSsw2C.png); position: relative; border-right: solid 1px #1F2630; border-bottom: solid 1px #1F2630; } #grid-header { text-align: right; } #score-board {} #timer { float: left; } .block { position: absolute; border: solid .5px black; } select { position: relative; border: solid 1px black; } </style> <!-- HTML --> <font color="black"> <h2>Tetris</h2> <div id="game-environs"> <div id="grid-header"> <div id="timer"><b>Timer:</b> <span id="time">0</span></div> <div id="score-board"><b>Score:</b> <span id="score">0</span></div> </div> <div id="grid" style="height: 500px; width: 250px;"></div> </div> <br> <br><b>Difficulty:</b> <select id="diff"> <option value="Easy">Easy</option> <option value="Medium">Medium</option> <option value="Hard">Hard</option> <option value="Custom">Custom</option> </select> <br> <br><b>Grid Size:</b> <select id="gridSize"> <option value="10x20">10x20</option> <option value="15x30">15x30</option> <option value="20x40">20x40</option> </select> <br> <div id="game-info"> <h2>Game Info</h2> <ul style="list-style-type:none;"> <li>Press the down arrow to start</li> <li>Press "P" to pause the game</li> <li>Use arrows keys to rotate and speed up block</li> <li>Press "Space" to instant place</li> <li>Press "R" to restart the game</li> </ul> </div> </font> <!-- JavaScript --> <script> /** * JavaScript source code for an implementation of Tetris. * * Authors: John P. Sprugeon, Jaden C. Bathon, Yuhao, Cici * Version: 5 (13 April 2019) * * Dependencies: * * 1. Document contains an element with id "grid". * 2. CSS (Cascading Style Sheet): * a. Desired background color is specified for grid. * b. Elements of "block" class are positioned "absolute". * * Enhancements * * 1. Score Recomended by Yuhao Coded by Jaden * 2. Restart Coded by Jaden * 4. Custom Speed Coded by Jaden * 5. Pause Co-Coded by Jaden and Cici * 6. Grid Size Recommended by CiCi, Coded by Jaden * 7. Audio Recommended by Yuhao, Co-Coded by Jaden and Yuhao * 8. Instant Place Coded by Jaden */ (function Tetris() { "use strict"; /***********/ /* GLOBALS */ /***********/ const UNITS = "px"; const StaticBlocks = []; let dynamicShape; let timerId; let playTime; let gameIsOver = false; let gameIsPaused = false; let gameIsStarted = false; let score = 0; let time = 0; let speed = 1000; let displayScoreDelay; const audio = new Audio('http://66.90.93.122/ost/tetris-gameboy-rip/mqvsagao/tetris-gameboy-02.mp3'); /****************************/ /* OBJECTS AND CONSTRUCTORS */ /****************************/ /** * Constructs a block. * * e.g. new Block({bgColor: "red", left: 0, right: 0}) */ function Block(options) { // Initialize the new block. const element = createBlockElement(); let left = options.left; let top = options.top; updatePosition(); /* Private Functions */ function updateLeft() { element.style.left = (left * Block.unitsWide) + UNITS; } function updateTop() { element.style.top = (top * Block.unitsTall) + UNITS; } function updatePosition() { updateLeft(); updateTop(); } function createBlockElement() { const block = document.createElement("div"); block.classList.add("block"); block.style.height = Block.height; block.style.width = Block.width; block.style.backgroundColor = options.bgColor; return block; } function canMoveLeft() { return left > 0 && !isOffLimits({ left: left - 1, top: top }); } function canMoveRight() { return left < Grid.blocksWide - 1 && !isOffLimits({ left: left + 1, top: top }); } function canMoveDown() { return top < Grid.blocksTall - 1 && !isOffLimits({ left: left, top: top + 1 }); } function moveLeft() { --left; updateLeft(); } function moveRight() { ++left; updateLeft(); } function moveDown() { ++top; updateTop(); } /* Public Methods */ this.removeFrom = function(parentElement) { parentElement.removeChild(element); } this.addTo = function(parentElement) { parentElement.append(element); }; this.moveLeft = function() { if (canMoveLeft()) moveLeft(); }; this.moveRight = function() { if (canMoveRight()) moveRight(); }; this.moveDown = function() { if (canMoveDown()) moveDown(); }; this.shiftDown = function() { moveDown(); } this.canMoveRight = canMoveRight; this.canMoveLeft = canMoveLeft; this.canMoveDown = canMoveDown; this.getLeft = function() { return left; } this.getTop = function() { return top; } this.getPosition = function() { return { left: left, top: top }; } this.setLeft = function(value) { left = value; updateLeft(); } this.setTop = function(value) { top = value; updateTop(); } } Block.subdivisions = 25; Block.unitsWide = Block.subdivisions; Block.unitsTall = Block.subdivisions; Block.size = Block.subdivisions + UNITS; Block.width = Block.size; Block.height = Block.size; Object.freeze(Block); /** * The grid that holds the game pieces. */ const Grid = { blocksWide: 10, blocksTall: 20 }; Grid.width = (Grid.blocksWide * Block.unitsWide) + UNITS; Grid.height = (Grid.blocksTall * Block.unitsTall) + UNITS; Grid.element = (function() { const element = document.getElementById("grid"); element.style.height = Grid.height; element.style.width = Grid.width; return element; })(); /** * Constructs a shape object composed of blocks. * The value of pIndex is the index of the block * around which the other blocks pivot when the * shape is rotated. If pIndex is undefined, the * shape does not rotate. */ function Shape(blocks, pIndex) { const numBlocks = blocks.length; const pivotBlock = (pIndex === undefined) ? null : blocks[pIndex]; blocks.forEach(function(block) { block.addTo(Grid.element); }); function canMoveRight() { if (gameIsOver) return false; for (let i = 0; i < numBlocks; ++i) { if (!blocks[i].canMoveRight()) return false; } return true; } function canMoveLeft() { if (gameIsOver) return false; for (let i = 0; i < numBlocks; ++i) { if (!blocks[i].canMoveLeft()) return false; } return true; } function canMoveDown() { if (gameIsOver) return false; for (let i = 0; i < numBlocks; ++i) { if (!blocks[i].canMoveDown()) return false; } return true; } function canPivot() { if (gameIsOver || pivotBlock === null) return false; const n = blocks.length; for (let i = 0; i < n; ++i) { const block = blocks[i]; const pos = getPivotedPosition(block); if (isOffLimits(pos)) return false; } return true; } function overlapsOtherShape() { const n = blocks.length; for (let i = 0; i < n; ++i) { const block = blocks[i]; const pos = block.getPosition(); if (isOffLimits(pos)) return true; } return false; } function moveDown() { for (let i = 0; i < numBlocks; ++i) blocks[i].moveDown(); } function moveLeft() { for (let i = 0; i < numBlocks; ++i) blocks[i].moveLeft(); } function moveRight() { for (let i = 0; i < numBlocks; ++i) blocks[i].moveRight(); } function freeze() { blocks.forEach(function(block) { StaticBlocks.push(block); }); StaticBlocks.sort(blockComparator); clearFullRows(); dynamicShape = getNextShape(); } function getPivotedPosition(block) { const B = [block.getTop(), block.getLeft()]; const P = [pivotBlock.getTop(), pivotBlock.getLeft()]; const Vr = [B[0] - P[0], B[1] - P[1]]; const Vt = [-1 * Vr[1], Vr[0]]; return { top: Vt[0] + P[0], left: Vt[1] + P[1] }; } function pivot(block) { // Credit: https://www.youtube.com/watch?v=Atlr5vvdchY if (block === pivotBlock || pivotBlock === null) return; const pos = getPivotedPosition(block); block.setTop(pos.top); block.setLeft(pos.left); } this.moveDown = function() { if (canMoveDown()) moveDown(); else freeze(); }; this.moveLeft = function() { if (canMoveLeft()) moveLeft(); }; this.moveRight = function() { if (canMoveRight()) moveRight(); }; this.overlapsOtherShape = overlapsOtherShape; this.canMoveRight = canMoveRight; this.canMoveLeft = canMoveLeft; this.canMoveDown = canMoveDown; this.freeze = freeze; this.pivot = function() { if (canPivot()) blocks.forEach(pivot); } } Shape.LColor = "darkorange"; Shape.JColor = "blue"; Shape.SColor = "green"; Shape.ZColor = "red"; Shape.TColor = "purple"; Shape.IColor = "deepskyblue"; Shape.OColor = "gold"; Object.freeze(Shape); /* Shape Subclasses */ function LShape() { Shape.call(this, getBlocks(), 1); function getBlocks() { return [ new Block({ bgColor: Shape.LColor, left: 3, top: 1 }), new Block({ bgColor: Shape.LColor, left: 4, top: 1 }), new Block({ bgColor: Shape.LColor, left: 5, top: 1 }), new Block({ bgColor: Shape.LColor, left: 5, top: 0 }) ]; } } function JShape() { Shape.call(this, getBlocks(), 1); function getBlocks() { return [ new Block({ bgColor: Shape.JColor, left: 3, top: 0 }), new Block({ bgColor: Shape.JColor, left: 4, top: 0 }), new Block({ bgColor: Shape.JColor, left: 5, top: 0 }), new Block({ bgColor: Shape.JColor, left: 5, top: 1 }) ]; } } function SShape() { Shape.call(this, getBlocks(), 2); function getBlocks() { return [ new Block({ bgColor: Shape.SColor, left: 4, top: 0 }), new Block({ bgColor: Shape.SColor, left: 5, top: 0 }), new Block({ bgColor: Shape.SColor, left: 5, top: 1 }), new Block({ bgColor: Shape.SColor, left: 6, top: 1 }) ]; } } function ZShape() { Shape.call(this, getBlocks(), 2); function getBlocks() { return [ new Block({ bgColor: Shape.ZColor, left: 4, top: 1 }), new Block({ bgColor: Shape.ZColor, left: 5, top: 1 }), new Block({ bgColor: Shape.ZColor, left: 5, top: 0 }), new Block({ bgColor: Shape.ZColor, left: 6, top: 0 }) ]; } } function TShape() { Shape.call(this, getBlocks(), 1); function getBlocks() { return [ new Block({ bgColor: Shape.TColor, left: 3, top: 1 }), new Block({ bgColor: Shape.TColor, left: 4, top: 1 }), new Block({ bgColor: Shape.TColor, left: 5, top: 1 }), new Block({ bgColor: Shape.TColor, left: 4, top: 0 }) ]; } } function OShape() { Shape.call(this, getBlocks(), undefined); function getBlocks() { return [ new Block({ bgColor: Shape.OColor, left: 4, top: 0 }), new Block({ bgColor: Shape.OColor, left: 5, top: 0 }), new Block({ bgColor: Shape.OColor, left: 4, top: 1 }), new Block({ bgColor: Shape.OColor, left: 5, top: 1 }) ]; } } function IShape() { Shape.call(this, getBlocks(), 2); function getBlocks() { return [ new Block({ bgColor: Shape.IColor, left: 3, top: 0 }), new Block({ bgColor: Shape.IColor, left: 4, top: 0 }), new Block({ bgColor: Shape.IColor, left: 5, top: 0 }), new Block({ bgColor: Shape.IColor, left: 6, top: 0 }) ]; } } /** * Used to display a user message. */ const ScoreBoard = Object.freeze({ delay: 50, element: document.getElementById("score"), setScore: function(value) { ScoreBoard.element.innerHTML = value; } }); const Timer = Object.freeze({ delay: 1000, element: document.getElementById("time"), setTime: function(value) { Timer.element.innerHTML = value; } }); const TimeMaster = Object.freeze({ incrementTime: function() { Timer.setTime(++time); } }); const ScoreKeeper = Object.freeze({ incrementScore: function() { ScoreBoard.setScore(++score); } }); /*************/ /* UTILITIES */ /*************/ function blockComparator(b1, b2) { const topDiff = b1.getTop() - b2.getTop(); return (topDiff === 0) ? b1.getLeft() - b2.getLeft() : topDiff; } function isOutOfBounds(pos) { return (pos.left < 0) || (pos.left > Grid.blocksWide - 1) || (pos.top < 0) || (pos.top > Grid.blocksTall - 1); return false; } function isOccupied(pos) { const n = StaticBlocks.length; for (let i = 0; i < n; ++i) { const block = StaticBlocks[i]; if (block.getLeft() === pos.left && block.getTop() === pos.top) return true; } return false; } function isOffLimits(pos) { return isOutOfBounds(pos) || isOccupied(pos); } function updateSpeed() { timerId = setInterval(function() { dynamicShape.moveDown(); }, speed); } function updateTime() { playTime = setInterval(TimeMaster.incrementTime, Timer.delay); } function difficulty() { let val = diff.value; if (val === "Easy") return 1000; else if (val === "Medium") return 500; else if (val === "Hard") return 250; else { let ans = parseInt(prompt("Please Input a speed")); if (ans > 0) return ans; else { alert("Invaild Speed!") diff.val = "Easy"; return 1000; } } } function updateGrid() { const val = gridSize.value; const newGrid = document.getElementById("grid"); if (val === "15x30") { newGrid.style.height = "750px"; newGrid.style.width = "375px"; Grid.blocksWide = 15; Grid.blocksTall = 30; } if (val === "20x40") { newGrid.style.height = "1000px"; newGrid.style.width = "500px"; Grid.blocksWide = 20; Grid.blocksTall = 40; } } function pauseGame() { if (!gameIsPaused) { clearInterval(timerId); clearInterval(playTime); audio.pause(); gameIsPaused = true; } else { speed = difficulty(); audio.play(); updateSpeed(); updateTime(); gameIsPaused = false; } } function onKeyDown(event) { const key = event.key; if (key === "ArrowLeft") { if (!gameIsOver) dynamicShape.moveLeft(); event.preventDefault(); } else if (key === "ArrowRight") { if (!gameIsOver) dynamicShape.moveRight(); event.preventDefault(); } else if (key === "ArrowDown") { if (!gameIsStarted) startGame(); else if (!gameIsOver) dynamicShape.moveDown(); event.preventDefault(); } else if (key === "ArrowUp") { if (!gameIsOver) dynamicShape.pivot(); event.preventDefault(); } else if (key === "p" || key === "P") { pauseGame(); } else if (key === "r" || key === "R") { gameIsOver = true; location.reload(); } else if (key === " ") { while (dynamicShape.canMoveDown()) { dynamicShape.moveDown() } } } function clearRow(blocks) { const rowNum = blocks[0].getTop(); blocks.forEach(function(block) { const i = StaticBlocks.indexOf(block); StaticBlocks.splice(i, 1); block.removeFrom(Grid.element); displayScoreDelay += ScoreBoard.delay; setTimeout(ScoreKeeper.incrementScore, displayScoreDelay); }); StaticBlocks.forEach(function(block) { if (block.getTop() < rowNum) block.shiftDown(); }); } function clearFullRows() { displayScoreDelay = 0; const rows = []; let row; const n = StaticBlocks.length; let first = 0, count = 0, rowNum = -1, firstBlock; for (let i = 0; i < n; ++i) { const block = StaticBlocks[i]; if (block.getTop() !== rowNum) { rowNum = block.getTop(); firstBlock = block; row = [block]; } else { row.push(block); if (row.length === Grid.blocksWide) { rows.push(row); } } } while (rows.length > 0) clearRow(rows.pop()); } function getNextShape() { const fxns = [LShape, JShape, SShape, ZShape, TShape, OShape, IShape]; const index = Math.floor(Math.random() * fxns.length); const fxn = fxns[index]; const nextShape = new fxn(); if (nextShape.overlapsOtherShape()) { gameIsOver = true; alert("Game Over"); audio.pause(); setTimeout(function() { alert("Press \"R\" to play again!"); }, 1000) clearInterval(timerId); } return nextShape; } function startGame() { updateGrid(); speed = difficulty(); gameIsStarted = true; dynamicShape.moveDown(); timerId = setInterval(function() { dynamicShape.moveDown(); }, speed); audio.loop = true; audio.play(); playTime = setInterval(TimeMaster.incrementTime, Timer.delay); } /*** MAIN ***/ window.addEventListener("keydown", onKeyDown); setTimeout(function() { dynamicShape = getNextShape(); }, 500); })(); </script>
Newer Post
Older Post
Home