let scheduler = new Scheduler() let settings = new Settings() let stats = new Stats() let holdQueue = new MinoesTable("holdTable") let matrix = new Matrix() let nextQueue = new NextQueue() let playing = false let lastActionSucceded = true let favicon window.onload = function(event) { document.selectedStyleSheetSet = selectedStyleSheet.title selectedStyleSheet.href = stylesheetSelect.value favicon = document.querySelector("link[rel~='icon']") restart() } function restart() { stats.modal.hide() holdQueue.init() stats.init() matrix.init() nextQueue.init() settings.init() pauseSettings() } function pauseSettings() { scheduler.clearInterval(fall) scheduler.clearTimeout(lockDown) scheduler.clearTimeout(repeat) scheduler.clearInterval(autorepeat) scheduler.clearInterval(ticktack) stats.pauseTime = stats.time document.onkeydown = null settings.show() } function newGame(event) { if (!settings.form.checkValidity()) { event.preventDefault() event.stopPropagation() settings.form.reportValidity() settings.form.classList.add('was-validated') } else { const audioContext = new AudioContext() for(const sound of document.getElementsByTagName("audio")) { sound.preservesPitch = false audioContext.createMediaElementSource(sound).connect(audioContext.destination) } levelInput.name = "level" levelInput.disabled = true titleHeader.innerHTML = "PAUSE" resumeButton.innerHTML = "Reprendre" event.target.onsubmit = resume stats.level = levelInput.valueAsNumber localStorage["startLevel"] = levelInput.value playing = true onblur = pauseSettings resume(event) } } function resume(event) { event.preventDefault() event.stopPropagation() settings.form.reportValidity() settings.form.classList.add('was-validated') if (settings.form.checkValidity()) { for(const sound of document.getElementsByTagName("audio")) sound.volume = sfxVolumeRange.value settings.modal.hide() settings.getInputs() document.onkeydown = onkeydown document.onkeyup = onkeyup stats.time = stats.pauseTime scheduler.setInterval(ticktack, 1000) if (matrix.piece) scheduler.setInterval(fall, stats.fallPeriod) else generate() } } function ticktack() { timeCell.innerText = stats.timeFormat.format(stats.time) } function generate(piece) { matrix.piece = piece || nextQueue.shift() lastActionSucceded = true favicon.href = matrix.piece.favicon_href if (matrix.piece.canMove(TRANSLATION.NONE)) { scheduler.setInterval(fall, stats.fallPeriod) } else { gameOver() // block out } } let playerActions = { moveLeft: () => matrix.piece.move(TRANSLATION.LEFT), moveRight: () => matrix.piece.move(TRANSLATION.RIGHT), rotateClockwise: () => matrix.piece.rotate(ROTATION.CW), rotateCounterclockwise: () => matrix.piece.rotate(ROTATION.CCW), softDrop: () => matrix.piece.move(TRANSLATION.DOWN) && ++stats.score, hardDrop: function() { scheduler.clearTimeout(lockDown) playSound(hardDropSound) while (matrix.piece.move(TRANSLATION.DOWN, ROTATION.NONE, true)) stats.score += 2 matrixCard.classList.add("hard-dropped-table-animation") lockDown() return true }, hold: function() { if (matrix.piece.holdEnabled) { scheduler.clearInterval(fall) scheduler.clearTimeout(lockDown) let heldPiece = holdQueue.piece matrix.piece.facing = FACING.NORTH matrix.piece.locked = false holdQueue.piece = matrix.piece holdQueue.piece.holdEnabled = false holdQueue.piece.locked = false generate(heldPiece) } }, pause: pauseSettings, } // Handle player inputs const REPEATABLE_ACTIONS = [ playerActions.moveLeft, playerActions.moveRight, playerActions.softDrop ] pressedKeys = new Set() actionsQueue = [] function onkeydown(event) { if (event.key in settings.keyBind) { event.preventDefault() if (!pressedKeys.has(event.key)) { pressedKeys.add(event.key) action = settings.keyBind[event.key] if (action()) { lastActionSucceded = true } else if (lastActionSucceded || !(action in REPEATABLE_ACTIONS)) { playSound(wallSound) lastActionSucceded = false } if (REPEATABLE_ACTIONS.includes(action)) { actionsQueue.unshift(action) scheduler.clearTimeout(repeat) scheduler.clearInterval(autorepeat) if (action == playerActions.softDrop) scheduler.setInterval(autorepeat, settings.fallPeriod/20) else scheduler.setTimeout(repeat, settings.das) } matrix.drawPiece() } } } function repeat() { if (actionsQueue.length) { actionsQueue[0]() scheduler.setInterval(autorepeat, settings.arr) } } function autorepeat() { if (actionsQueue.length) { if (actionsQueue[0]()) { lastActionSucceded = true } else if (lastActionSucceded) { wallSound.play() lastActionSucceded = false } } else scheduler.clearInterval(autorepeat) } function onkeyup(event) { if (event.key in settings.keyBind) { event.preventDefault() pressedKeys.delete(event.key) action = settings.keyBind[event.key] if (actionsQueue.includes(action)) { actionsQueue.splice(actionsQueue.indexOf(action), 1) scheduler.clearTimeout(repeat) scheduler.clearInterval(autorepeat) if (actionsQueue.length) { if (action == playerActions.softDrop) scheduler.setInterval(autorepeat, settings.fallPeriod/20) else scheduler.setTimeout(repeat, settings.das) } else { matrix.drawPiece() } } } } function fall() { matrix.piece.move(TRANSLATION.DOWN) } function lockDown() { scheduler.clearTimeout(lockDown) scheduler.clearInterval(fall) if (matrix.lock()) { stats.lockDown(matrix.piece.tSpin, matrix.clearLines()) generate() } else { gameOver() // lock out } } onanimationend = function (event) { event.target.classList.remove(event.animationName) } messagesSpan.onanimationend = function(event) { event.target.remove() } function gameOver() { matrix.piece.locked = false matrix.drawPiece() document.onkeydown = null onblur = null scheduler.clearInterval(ticktack) playing = false stats.show() } window.onbeforeunload = function(event) { stats.save() settings.save() if (playing) return false; } if ('serviceWorker' in navigator) { navigator.serviceWorker.register('service-worker.js'); }