change GUI
This commit is contained in:
parent
829b8094eb
commit
3aeec768cb
665
app.js
665
app.js
@ -1,5 +1,6 @@
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
||||
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
|
||||
import * as FPS from 'three/addons/libs/stats.module.js';
|
||||
|
||||
let P = (x, y, z = 0) => new THREE.Vector3(x, y, z)
|
||||
@ -15,7 +16,7 @@ HTMLElement.prototype.addNewChild = function (tag, properties) {
|
||||
}
|
||||
|
||||
|
||||
/* Contants */
|
||||
/* Constants */
|
||||
|
||||
const ROWS = 24
|
||||
const SKYLINE = 20
|
||||
@ -251,7 +252,7 @@ class MinoMaterial extends THREE.MeshBasicMaterial {
|
||||
color: color,
|
||||
reflectivity: 0.95,
|
||||
envMap: minoRenderTarget.texture,
|
||||
roughness: 0,
|
||||
roughness: 0.1,
|
||||
metalness: 0.25
|
||||
})
|
||||
}
|
||||
@ -324,7 +325,7 @@ class Tetromino extends THREE.Group {
|
||||
|
||||
move(translation, testFacing) {
|
||||
if (this.canMove(translation, testFacing)) {
|
||||
scheduler.clearTimeout(lockDown)
|
||||
scheduler.clearTimeout(game.lockDown)
|
||||
this.position.add(translation)
|
||||
if (!testFacing) {
|
||||
this.rotatedLast = false
|
||||
@ -336,13 +337,13 @@ class Tetromino extends THREE.Group {
|
||||
} else {
|
||||
this.locked = true
|
||||
scene.remove(ghost)
|
||||
scheduler.setTimeout(lockDown, stats.lockDelay)
|
||||
scheduler.setTimeout(game.lockDown, stats.lockDelay)
|
||||
}
|
||||
return true
|
||||
} else if (translation == TRANSLATION.DOWN) {
|
||||
this.locked = true
|
||||
if (!scheduler.timeoutTasks.has(lockDown))
|
||||
scheduler.setTimeout(lockDown, stats.lockDelay)
|
||||
if (!scheduler.timeoutTasks.has(game.lockDown))
|
||||
scheduler.setTimeout(game.lockDown, stats.lockDelay)
|
||||
}
|
||||
}
|
||||
|
||||
@ -488,95 +489,124 @@ Ghost.prototype.minoesPosition = [
|
||||
[P(0, 0, 0), P(0, 0, 0), P(0, 0, 0), P(0, 0, 0)],
|
||||
]
|
||||
|
||||
|
||||
class Settings {
|
||||
constructor() {
|
||||
this.form = settingsForm
|
||||
this.load()
|
||||
this.modal = new bootstrap.Modal('#settingsModal')
|
||||
settingsModal.addEventListener('shown.bs.modal', () => {
|
||||
resumeButton.focus()
|
||||
})
|
||||
}
|
||||
|
||||
load() {
|
||||
for (let input of settingsForm.elements) {
|
||||
if (input.name) {
|
||||
if (localStorage[input.name]) input.value = localStorage[input.name]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
for (let element of settingsForm.elements) {
|
||||
if (element.name) {
|
||||
localStorage[element.name] = element.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.form.onsubmit = newGame
|
||||
levelInput.name = "startLevel"
|
||||
levelInput.disabled = false
|
||||
//titleHeader.innerHTML = "teTra"
|
||||
resumeButton.innerHTML = "Jouer"
|
||||
}
|
||||
|
||||
show() {
|
||||
resumeButton.disabled = false
|
||||
settings.form.classList.remove('was-validated')
|
||||
settings.modal.show()
|
||||
settings.form.reportValidity()
|
||||
}
|
||||
|
||||
getInputs() {
|
||||
for (let input of this.form.querySelectorAll("input[type='text']")) {
|
||||
this[input.name] = KEY_NAMES[input.value] || input.value
|
||||
}
|
||||
for (let input of this.form.querySelectorAll("input[type='number'], input[type='range']")) {
|
||||
this[input.name] = input.valueAsNumber
|
||||
}
|
||||
for (let input of this.form.querySelectorAll("input[type='checkbox']")) {
|
||||
this[input.name] = input.checked == true
|
||||
}
|
||||
|
||||
this.keyBind = {}
|
||||
for (let actionName in playerActions) {
|
||||
this.keyBind[settings[actionName]] = playerActions[actionName]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.changeKey = function (input) {
|
||||
let prevValue = input.value
|
||||
input.value = ""
|
||||
function changeKey() {
|
||||
let controller = this
|
||||
let input = controller.domElement.getElementsByTagName("input")[0]
|
||||
input.select()
|
||||
input.onkeydown = function (event) {
|
||||
event.preventDefault()
|
||||
input.value = KEY_NAMES[event.key] || event.key
|
||||
controller.setValue(KEY_NAMES[event.key] || event.key)
|
||||
input.blur()
|
||||
}
|
||||
input.onblur = function (event) {
|
||||
if (input.value == "") input.value = prevValue
|
||||
input.onkeydown = null
|
||||
input.onblur = null
|
||||
settings.bindKeys()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Settings {
|
||||
constructor(gui) {
|
||||
this.startLevel = 1
|
||||
|
||||
this.moveLeft = "←"
|
||||
this.moveRight = "→"
|
||||
this.rotateCCW = "w"
|
||||
this.rotateCW = "↑"
|
||||
this.softDrop = "↓"
|
||||
this.hardDrop = "Espace"
|
||||
this.hold = "c"
|
||||
this.pause = "Échap."
|
||||
|
||||
this.arrDelay = 50
|
||||
this.dasDelay = 300
|
||||
|
||||
this.musicVolume = 50
|
||||
this.sfxVolume = 50
|
||||
|
||||
this.gui = gui.addFolder("Options").close()
|
||||
|
||||
this.gui.add(this, "startLevel").name("Niveau initial").min(1).max(15).step(1)
|
||||
|
||||
this.gui.keyFolder = this.gui.addFolder("Commandes").open()
|
||||
let moveLeftController = this.gui.keyFolder.add(this,"moveLeft").name('Gauche')
|
||||
moveLeftController.domElement.onclick = changeKey.bind(moveLeftController)
|
||||
let moveRightController = this.gui.keyFolder.add(this,"moveRight").name('Droite')
|
||||
moveRightController.domElement.onclick = changeKey.bind(moveRightController)
|
||||
let rotateCWController = this.gui.keyFolder.add(this,"rotateCW").name('Rotation horaire')
|
||||
rotateCWController.domElement.onclick = changeKey.bind(rotateCWController)
|
||||
let rotateCCWController = this.gui.keyFolder.add(this,"rotateCCW").name('anti-horaire')
|
||||
rotateCCWController.domElement.onclick = changeKey.bind(rotateCCWController)
|
||||
let softDropController = this.gui.keyFolder.add(this,"softDrop").name('Chute lente')
|
||||
softDropController.domElement.onclick = changeKey.bind(softDropController)
|
||||
let hardDropController = this.gui.keyFolder.add(this,"hardDrop").name('Chute rapide')
|
||||
hardDropController.domElement.onclick = changeKey.bind(hardDropController)
|
||||
let holdController = this.gui.keyFolder.add(this,"hold").name('Garder')
|
||||
holdController.domElement.onclick = changeKey.bind(holdController)
|
||||
let pauseController = this.gui.keyFolder.add(this,"pause").name('Pause')
|
||||
pauseController.domElement.onclick = changeKey.bind(pauseController)
|
||||
|
||||
this.gui.delayFolder = this.gui.addFolder("Répétition automatique").open()
|
||||
this.gui.delayFolder.add(this,"arrDelay").name("ARR (ms)").min(2).max(200).step(1);
|
||||
this.gui.delayFolder.add(this,"dasDelay").name("DAS (ms)").min(100).max(500).step(5);
|
||||
|
||||
this.gui.volumeFolder = this.gui.addFolder("Volume").open()
|
||||
this.gui.volumeFolder.add(this,"musicVolume").name("Musique").min(0).max(100).step(1).onChange((volume) => {
|
||||
music.setVolume(volume/100)
|
||||
})
|
||||
this.gui.volumeFolder.add(this,"sfxVolume").name("SFX").min(0).max(100).step(1).onChange((volume) => {
|
||||
lineClearSound.setVolume(volume/100)
|
||||
tetrisSound.setVolume(volume/100)
|
||||
hardDropSound.setVolume(volume/100)
|
||||
})
|
||||
|
||||
this.load()
|
||||
this.bindKeys()
|
||||
}
|
||||
|
||||
bindKeys() {
|
||||
this.keyBind = {}
|
||||
for (let actionName in playerActions) {
|
||||
this.keyBind[KEY_NAMES[this[actionName]] || this[actionName]] = playerActions[actionName]
|
||||
}
|
||||
}
|
||||
|
||||
load() {
|
||||
if (localStorage["teTraSettings"]) this.gui.load(JSON.parse(localStorage["teTraSettings"]))
|
||||
}
|
||||
|
||||
save() {
|
||||
localStorage["teTraSettings"] = JSON.stringify(this.gui.save())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Stats {
|
||||
constructor() {
|
||||
this.modal = new bootstrap.Modal('#statsModal')
|
||||
this.load()
|
||||
}
|
||||
constructor(parentGui) {
|
||||
this.clock = new THREE.Clock(false)
|
||||
this.clock.timeFormat = new Intl.DateTimeFormat("fr-FR", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZone: "UTC"
|
||||
})
|
||||
this.elapsedTime = 0
|
||||
|
||||
load() {
|
||||
this.highScore = Number(localStorage["highScore"]) || 0
|
||||
this.init()
|
||||
|
||||
this.gui = parentGui.addFolder("Stats")
|
||||
this.gui.add(this, "level").name("Niveau").disable().listen()
|
||||
this.gui.add(this, "goal").name("Objectif").disable().listen()
|
||||
this.gui.add(this, "score").name("Score").disable().listen()
|
||||
this.gui.add(this, "highScore").name("Meilleur score").disable().listen()
|
||||
this.gui.timeController = this.gui.add(this, "time").name("Temps").disable().listen()
|
||||
}
|
||||
|
||||
init() {
|
||||
this.score = 0
|
||||
this._level = 0
|
||||
this._score = 0
|
||||
this.goal = 0
|
||||
this.highScore = Number(localStorage["teTraHighScore"]) || 0
|
||||
this.combo = 0
|
||||
this.b2b = 0
|
||||
this.startTime = new Date()
|
||||
@ -593,7 +623,6 @@ class Stats {
|
||||
if (score > this.highScore) {
|
||||
this.highScore = score
|
||||
}
|
||||
scoreDiv.innerText = score.toLocaleString()
|
||||
}
|
||||
|
||||
get score() {
|
||||
@ -603,29 +632,15 @@ class Stats {
|
||||
set level(level) {
|
||||
this._level = level
|
||||
this.goal += level * 5
|
||||
if (level <= 20) {
|
||||
this.fallPeriod = 1000 * Math.pow(0.8 - ((level - 1) * 0.007), level - 1)
|
||||
}
|
||||
if (level > 15)
|
||||
this.lockDelay = 500 * Math.pow(0.9, level - 15)
|
||||
levelInput.value = level
|
||||
if (level <= 20) this.fallPeriod = 1000 * Math.pow(0.8 - ((level - 1) * 0.007), level - 1)
|
||||
if (level > 15) this.lockDelay = 500 * Math.pow(0.9, level - 15)
|
||||
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>NIVEAU<br/>${this.level}</h1>` })
|
||||
levelDiv.innerText = level
|
||||
}
|
||||
|
||||
get level() {
|
||||
return this._level
|
||||
}
|
||||
|
||||
set goal(goal) {
|
||||
this._goal = goal
|
||||
goalDiv.innerText = goal
|
||||
}
|
||||
|
||||
get goal() {
|
||||
return this._goal
|
||||
}
|
||||
|
||||
set combo(combo) {
|
||||
this._combo = combo
|
||||
if (combo > this.maxCombo) this.maxCombo = combo
|
||||
@ -644,12 +659,8 @@ class Stats {
|
||||
return this._b2b
|
||||
}
|
||||
|
||||
set time(time) {
|
||||
this.startTime = new Date() - time
|
||||
}
|
||||
|
||||
get time() {
|
||||
return new Date() - this.startTime
|
||||
return this.clock.timeFormat.format(this.clock.elapsedTime * 1000)
|
||||
}
|
||||
|
||||
lockDown(nbClearedLines, tSpin) {
|
||||
@ -735,53 +746,23 @@ class Stats {
|
||||
this.goal -= awardedLineClears
|
||||
if (this.goal <= 0) this.level++
|
||||
}
|
||||
|
||||
show() {
|
||||
let time = stats.time
|
||||
statsModalScoreCell.innerText = this.score.toLocaleString()
|
||||
statsModalHighScoreCell.innerText = this.highScore.toLocaleString()
|
||||
statsModalLevelCell.innerText = this.level
|
||||
statsModalTimeCell.innerText = this.timeFormat.format(time)
|
||||
statsModaltotalClearedLines.innerText = this.totalClearedLines
|
||||
statsModaltotalClearedLinesPM.innerText = (stats.totalClearedLines * 60000 / time).toFixed(2)
|
||||
statsModalNbQuatris.innerText = this.nbQuatris
|
||||
statsModalNbTSpin.innerText = this.nbTSpin
|
||||
statsModalMaxCombo.innerText = this.maxCombo
|
||||
statsModalMaxB2B.innerText = this.maxB2B
|
||||
this.modal.show()
|
||||
}
|
||||
|
||||
save() {
|
||||
localStorage["highScore"] = this.highScore
|
||||
}
|
||||
}
|
||||
Stats.prototype.timeFormat = new Intl.DateTimeFormat("fr-FR", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
timeZone: "UTC"
|
||||
})
|
||||
|
||||
function tick() {
|
||||
timeDiv.innerText = stats.timeFormat.format(stats.time)
|
||||
}
|
||||
|
||||
|
||||
/* Scene */
|
||||
|
||||
const manager = new THREE.LoadingManager()
|
||||
manager.onStart = function (url, itemsLoaded, itemsTotal) {
|
||||
loadingBar.style.setProperty("width", '0%')
|
||||
const loadManager = new THREE.LoadingManager()
|
||||
loadManager.onStart = function (url, itemsLoaded, itemsTotal) {
|
||||
loadingPercent.innerText = "0%"
|
||||
}
|
||||
manager.onProgress = function (url, itemsLoaded, itemsTotal) {
|
||||
loadingBar.style.setProperty("width", 100 * itemsLoaded / itemsTotal + '%')
|
||||
loadManager.onProgress = function (url, itemsLoaded, itemsTotal) {
|
||||
loadingPercent.innerText = 100 * itemsLoaded / itemsTotal + '%'
|
||||
}
|
||||
manager.onLoad = function () {
|
||||
restart()
|
||||
messagesSpan.innerHTML = ""
|
||||
loadManager.onLoad = function () {
|
||||
loaddingCircle.remove()
|
||||
renderer.setAnimationLoop(animate)
|
||||
}
|
||||
manager.onError = function (url) {
|
||||
loadManager.onError = function (url) {
|
||||
messagesSpan.innerHTML = 'Erreur de chargement'
|
||||
}
|
||||
|
||||
@ -835,7 +816,7 @@ const colorFullOpacity = 0.2
|
||||
const commonCylinderGeometry = new THREE.CylinderGeometry(25, 25, 500, 12, 1, true)
|
||||
|
||||
// dark space full of stars - background cylinder
|
||||
const darkCylinderTexture = new THREE.TextureLoader(manager).load("images/dark.jpg")
|
||||
const darkCylinderTexture = new THREE.TextureLoader(loadManager).load("images/dark.jpg")
|
||||
darkCylinderTexture.wrapS = THREE.RepeatWrapping
|
||||
darkCylinderTexture.wrapT = THREE.MirroredRepeatWrapping
|
||||
darkCylinderTexture.repeat.set(1, 1)
|
||||
@ -853,7 +834,7 @@ darkCylinder.position.set(5, 10, -10)
|
||||
scene.add(darkCylinder)
|
||||
|
||||
// colourfull space full of nebulas - main universe cylinder
|
||||
const colorFullCylinderTexture = new THREE.TextureLoader(manager).load("images/colorfull.jpg")
|
||||
const colorFullCylinderTexture = new THREE.TextureLoader(loadManager).load("images/colorfull.jpg")
|
||||
colorFullCylinderTexture.wrapS = THREE.RepeatWrapping
|
||||
colorFullCylinderTexture.wrapT = THREE.MirroredRepeatWrapping
|
||||
colorFullCylinderTexture.repeat.set(1, 1)
|
||||
@ -925,19 +906,6 @@ const hardDroppedMatrix = mixer.clipAction(clip)
|
||||
hardDroppedMatrix.loop = THREE.LoopOnce
|
||||
hardDroppedMatrix.setDuration(0.2)
|
||||
|
||||
|
||||
const lineClearSound = new Audio("audio/line-clear.wav")
|
||||
const tetrisSound = new Audio("audio/tetris.wav")
|
||||
const hardDropSound = new Audio("audio/hard-drop.wav")
|
||||
const music = new Audio("https://iterations.org/files/music/remixes/Tetris_CheDDer_OC_ReMix.mp3")
|
||||
music.loop = true
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
camera.aspect = window.innerWidth / window.innerHeight
|
||||
camera.updateProjectionMatrix()
|
||||
})
|
||||
|
||||
let clock = new THREE.Clock()
|
||||
|
||||
function animate() {
|
||||
@ -961,9 +929,14 @@ function animate() {
|
||||
minoCamera.update(renderer, scene)
|
||||
|
||||
if (showFPS) fps.update();
|
||||
|
||||
}
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
camera.aspect = window.innerWidth / window.innerHeight
|
||||
camera.updateProjectionMatrix()
|
||||
})
|
||||
|
||||
|
||||
/* Game logic */
|
||||
|
||||
@ -971,16 +944,14 @@ messagesSpan.onanimationend = function (event) {
|
||||
event.target.remove()
|
||||
}
|
||||
|
||||
let scheduler = new Scheduler()
|
||||
let settings = new Settings()
|
||||
let stats = new Stats()
|
||||
let playing = false
|
||||
//let favicon = document.querySelector("link[rel~='icon']")
|
||||
let piece = null
|
||||
|
||||
let game = {
|
||||
init: function() {
|
||||
this.playing = false
|
||||
|
||||
window.restart = function () {
|
||||
stats.modal.hide()
|
||||
stats.init()
|
||||
settings.init()
|
||||
|
||||
holdQueue.remove(holdQueue.piece)
|
||||
holdQueue.piece = null
|
||||
if (nextQueue.pieces) nextQueue.pieces.forEach(piece => nextQueue.remove(piece))
|
||||
@ -990,79 +961,35 @@ window.restart = function () {
|
||||
piece = null
|
||||
scene.remove(ghost)
|
||||
music.currentTime = 0
|
||||
pauseSettings()
|
||||
}
|
||||
},
|
||||
|
||||
function pauseSettings() {
|
||||
stats.pauseTime = stats.time
|
||||
start: function() {
|
||||
startButton.hide()
|
||||
|
||||
scheduler.clearInterval(fall)
|
||||
scheduler.clearTimeout(lockDown)
|
||||
scheduler.clearTimeout(repeat)
|
||||
scheduler.clearInterval(autorepeat)
|
||||
scheduler.clearInterval(tick)
|
||||
this.playing = true
|
||||
stats.clock.start()
|
||||
|
||||
music.pause()
|
||||
document.onkeydown = null
|
||||
onblur = this.pause
|
||||
|
||||
settings.show()
|
||||
}
|
||||
|
||||
function newGame(event) {
|
||||
if (!settings.form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
settings.form.reportValidity()
|
||||
settings.form.classList.add('was-validated')
|
||||
} else {
|
||||
levelInput.name = "level"
|
||||
levelInput.disabled = true
|
||||
//titleHeader.innerHTML = "PAUSE"
|
||||
resumeButton.innerHTML = "Reprendre"
|
||||
event.target.onsubmit = resume
|
||||
nextQueue.init()
|
||||
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()) {
|
||||
settings.modal.hide()
|
||||
settings.getInputs()
|
||||
renderer.domElement.focus()
|
||||
stats.level = settings.startLevel
|
||||
this.resume()
|
||||
},
|
||||
|
||||
resume: function(event) {
|
||||
document.onkeydown = onkeydown
|
||||
document.onkeyup = onkeyup
|
||||
|
||||
stats.time = stats.pauseTime
|
||||
|
||||
lineClearSound.volume = settings.sfxVolume
|
||||
tetrisSound.volume = settings.sfxVolume
|
||||
hardDropSound.volume = settings.sfxVolume
|
||||
if (settings.musicVolume > 0) {
|
||||
music.volume = settings.musicVolume
|
||||
stats.clock.start()
|
||||
stats.clock.elapsedTime = stats.elapsedTime
|
||||
music.play()
|
||||
}
|
||||
|
||||
scheduler.setInterval(tick)
|
||||
if (piece) scheduler.setInterval(game.fall, stats.fallPeriod)
|
||||
else this.generate()
|
||||
},
|
||||
|
||||
if (piece) scheduler.setInterval(fall, stats.fallPeriod)
|
||||
else generate()
|
||||
}
|
||||
}
|
||||
|
||||
var piece = null
|
||||
function generate(heldPiece) {
|
||||
generate: function(heldPiece) {
|
||||
if (heldPiece) {
|
||||
piece = heldPiece
|
||||
} else {
|
||||
@ -1074,121 +1001,19 @@ function generate(heldPiece) {
|
||||
scene.add(ghost)
|
||||
|
||||
if (piece.canMove(TRANSLATION.NONE)) {
|
||||
scheduler.setInterval(fall, stats.fallPeriod)
|
||||
scheduler.setInterval(game.fall, stats.fallPeriod)
|
||||
} else {
|
||||
gameOver() // block out
|
||||
}
|
||||
}
|
||||
|
||||
let playerActions = {
|
||||
moveLeft: () => piece.move(TRANSLATION.LEFT),
|
||||
|
||||
moveRight: () => piece.move(TRANSLATION.RIGHT),
|
||||
|
||||
rotateClockwise: () => piece.rotate(ROTATION.CW),
|
||||
|
||||
rotateCounterclockwise: () => piece.rotate(ROTATION.CCW),
|
||||
|
||||
softDrop: function () {
|
||||
if (piece.move(TRANSLATION.DOWN)) stats.score++
|
||||
},
|
||||
|
||||
hardDrop: function () {
|
||||
scheduler.clearTimeout(lockDown)
|
||||
hardDropSound.play()
|
||||
if (settings.sfxVolume) {
|
||||
hardDropSound.currentTime = 0
|
||||
hardDropSound.play()
|
||||
}
|
||||
while (piece.move(TRANSLATION.DOWN)) stats.score += 2
|
||||
lockDown()
|
||||
hardDroppedMatrix.reset()
|
||||
hardDroppedMatrix.play()
|
||||
},
|
||||
|
||||
hold: function () {
|
||||
if (piece.holdEnabled) {
|
||||
scheduler.clearInterval(fall)
|
||||
scheduler.clearTimeout(lockDown)
|
||||
|
||||
let heldpiece = holdQueue.piece
|
||||
holdQueue.piece = piece
|
||||
holdQueue.piece.holdEnabled = false
|
||||
holdQueue.piece.locked = false
|
||||
holdQueue.piece.position.set(0, 0)
|
||||
holdQueue.piece.facing = FACING.NORTH
|
||||
holdQueue.add(holdQueue.piece)
|
||||
generate(heldpiece)
|
||||
game.over() // block out
|
||||
}
|
||||
},
|
||||
|
||||
pause: pauseSettings,
|
||||
}
|
||||
|
||||
// Handle player inputs
|
||||
const REPEATABLE_ACTIONS = [
|
||||
playerActions.moveLeft,
|
||||
playerActions.moveRight,
|
||||
playerActions.softDrop
|
||||
]
|
||||
let pressedKeys = new Set()
|
||||
let actionsQueue = []
|
||||
|
||||
function onkeydown(event) {
|
||||
if (event.key in settings.keyBind) {
|
||||
event.preventDefault()
|
||||
if (!pressedKeys.has(event.key)) {
|
||||
pressedKeys.add(event.key)
|
||||
let action = settings.keyBind[event.key]
|
||||
action()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function repeat() {
|
||||
if (actionsQueue.length) {
|
||||
actionsQueue[0]()
|
||||
scheduler.setInterval(autorepeat, settings.arr)
|
||||
}
|
||||
}
|
||||
|
||||
function autorepeat() {
|
||||
if (actionsQueue.length) {
|
||||
actionsQueue[0]()
|
||||
} else {
|
||||
scheduler.clearInterval(autorepeat)
|
||||
}
|
||||
}
|
||||
|
||||
function onkeyup(event) {
|
||||
if (event.key in settings.keyBind) {
|
||||
event.preventDefault()
|
||||
pressedKeys.delete(event.key)
|
||||
let action = settings.keyBind[event.key]
|
||||
if (actionsQueue.includes(action)) {
|
||||
actionsQueue.splice(actionsQueue.indexOf(action), 1)
|
||||
if (!actionsQueue.length) {
|
||||
scheduler.clearTimeout(repeat)
|
||||
scheduler.clearInterval(autorepeat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fall() {
|
||||
fall: function() {
|
||||
piece.move(TRANSLATION.DOWN)
|
||||
}
|
||||
},
|
||||
|
||||
function lockDown() {
|
||||
scheduler.clearTimeout(lockDown)
|
||||
scheduler.clearInterval(fall)
|
||||
lockDown: function() {
|
||||
scheduler.clearTimeout(game.lockDown)
|
||||
scheduler.clearInterval(game.fall)
|
||||
|
||||
if (matrix.lock(piece)) {
|
||||
scene.remove(piece)
|
||||
@ -1205,29 +1030,185 @@ function lockDown() {
|
||||
}
|
||||
stats.lockDown(nbClearedLines, tSpin)
|
||||
|
||||
generate()
|
||||
game.generate()
|
||||
} else {
|
||||
gameOver() // lock out
|
||||
game.over() // lock out
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
function gameOver() {
|
||||
pause: function() {
|
||||
stats.elapsedTime = stats.clock.elapsedTime
|
||||
stats.clock.stop()
|
||||
|
||||
scheduler.clearInterval(game.fall)
|
||||
scheduler.clearTimeout(game.lockDown)
|
||||
scheduler.clearTimeout(repeat)
|
||||
scheduler.clearInterval(autorepeat)
|
||||
|
||||
music.pause()
|
||||
document.onkeydown = null
|
||||
renderer.domElement.tabIndex = 1
|
||||
renderer.domElement.onfocus = game.resume
|
||||
|
||||
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>PAUSE</h1>` })
|
||||
},
|
||||
|
||||
over: function() {
|
||||
piece.locked = false
|
||||
|
||||
document.onkeydown = null
|
||||
onblur = null
|
||||
playing = false
|
||||
renderer.domElement.onblur = null
|
||||
renderer.domElement.onfocus = null
|
||||
game.playing = false
|
||||
music.pause()
|
||||
stats.clock.stop()
|
||||
localStorage["teTraHighScore"] = stats.highScore
|
||||
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>GAME<br/>OVER</h1>` })
|
||||
},
|
||||
}
|
||||
|
||||
scheduler.clearInterval(tick)
|
||||
let playerActions = {
|
||||
moveLeft: () => piece.move(TRANSLATION.LEFT),
|
||||
|
||||
stats.show()
|
||||
moveRight: () => piece.move(TRANSLATION.RIGHT),
|
||||
|
||||
rotateCW: () => piece.rotate(ROTATION.CW),
|
||||
|
||||
rotateCCW: () => piece.rotate(ROTATION.CCW),
|
||||
|
||||
softDrop: function () {
|
||||
if (piece.move(TRANSLATION.DOWN)) stats.score++
|
||||
},
|
||||
|
||||
hardDrop: function () {
|
||||
scheduler.clearTimeout(game.lockDown)
|
||||
hardDropSound.play()
|
||||
if (settings.sfxVolume) {
|
||||
hardDropSound.currentTime = 0
|
||||
hardDropSound.play()
|
||||
}
|
||||
while (piece.move(TRANSLATION.DOWN)) stats.score += 2
|
||||
game.lockDown()
|
||||
hardDroppedMatrix.reset()
|
||||
hardDroppedMatrix.play()
|
||||
},
|
||||
|
||||
hold: function () {
|
||||
if (piece.holdEnabled) {
|
||||
scheduler.clearInterval(game.fall)
|
||||
scheduler.clearTimeout(game.lockDown)
|
||||
|
||||
let heldpiece = holdQueue.piece
|
||||
holdQueue.piece = piece
|
||||
holdQueue.piece.holdEnabled = false
|
||||
holdQueue.piece.locked = false
|
||||
holdQueue.piece.position.set(0, 0)
|
||||
holdQueue.piece.facing = FACING.NORTH
|
||||
holdQueue.add(holdQueue.piece)
|
||||
game.generate(heldpiece)
|
||||
}
|
||||
},
|
||||
|
||||
pause: game.pause,
|
||||
}
|
||||
|
||||
// Sounds
|
||||
const listener = new THREE.AudioListener()
|
||||
camera.add( listener )
|
||||
const audioLoader = new THREE.AudioLoader()
|
||||
const music = new THREE.Audio(listener)
|
||||
audioLoader.load('audio/Tetris_CheDDer_OC_ReMix.mp3', function( buffer ) {
|
||||
music.setBuffer(buffer)
|
||||
music.setLoop(true)
|
||||
music.setVolume(settings.musicVolume/100)
|
||||
music.play()
|
||||
})
|
||||
const lineClearSound = new THREE.Audio(listener)
|
||||
audioLoader.load('audio/line-clear.wav', function( buffer ) {
|
||||
lineClearSound.setBuffer(buffer)
|
||||
lineClearSound.setVolume(settings.sfxVolume/100)
|
||||
})
|
||||
const tetrisSound = new THREE.Audio(listener)
|
||||
audioLoader.load('audio/tetris.wav', function( buffer ) {
|
||||
tetrisSound.setBuffer(buffer)
|
||||
tetrisSound.setVolume(settings.sfxVolume/100)
|
||||
})
|
||||
const hardDropSound = new THREE.Audio(listener)
|
||||
audioLoader.load('audio/hard-drop.wav', function( buffer ) {
|
||||
hardDropSound.setBuffer(buffer)
|
||||
hardDropSound.setVolume(settings.sfxVolume/100)
|
||||
})
|
||||
|
||||
let scheduler = new Scheduler()
|
||||
var gui = new GUI().title("teTra")
|
||||
let startButton = gui.add(game, "start").name("Démarrer")
|
||||
let settings = new Settings(gui)
|
||||
let stats = new Stats(gui)
|
||||
|
||||
game.init()
|
||||
|
||||
// Handle player inputs
|
||||
const REPEATABLE_ACTIONS = [
|
||||
playerActions.moveLeft,
|
||||
playerActions.moveRight,
|
||||
playerActions.softDrop
|
||||
]
|
||||
let pressedKeys = new Set()
|
||||
let actionsQueue = []
|
||||
|
||||
function onkeydown(event) {
|
||||
let key = event.key
|
||||
if (key in settings.keyBind) {
|
||||
event.preventDefault()
|
||||
if (!pressedKeys.has(key)) {
|
||||
pressedKeys.add(key)
|
||||
let action = settings.keyBind[key]
|
||||
action()
|
||||
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.dasDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function repeat() {
|
||||
if (actionsQueue.length) {
|
||||
actionsQueue[0]()
|
||||
scheduler.setInterval(autorepeat, settings.arrDelay)
|
||||
}
|
||||
}
|
||||
|
||||
function autorepeat() {
|
||||
if (actionsQueue.length) {
|
||||
actionsQueue[0]()
|
||||
} else {
|
||||
scheduler.clearInterval(autorepeat)
|
||||
}
|
||||
}
|
||||
|
||||
function onkeyup(event) {
|
||||
let key = event.key
|
||||
if (key in settings.keyBind) {
|
||||
event.preventDefault()
|
||||
pressedKeys.delete(key)
|
||||
let action = settings.keyBind[key]
|
||||
if (actionsQueue.includes(action)) {
|
||||
actionsQueue.splice(actionsQueue.indexOf(action), 1)
|
||||
if (!actionsQueue.length) {
|
||||
scheduler.clearTimeout(repeat)
|
||||
scheduler.clearInterval(autorepeat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.onbeforeunload = function (event) {
|
||||
stats.save()
|
||||
settings.save()
|
||||
if (playing) return false
|
||||
if (game.playing) return false
|
||||
}
|
||||
|
||||
|
||||
|
BIN
audio/Tetris_CheDDer_OC_ReMix.mp3
Normal file
BIN
audio/Tetris_CheDDer_OC_ReMix.mp3
Normal file
Binary file not shown.
159
gui.html
Normal file
159
gui.html
Normal file
@ -0,0 +1,159 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css">
|
||||
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://unpkg.com/three@0.152.2/build/three.module.js?module",
|
||||
"three/addons/": "https://unpkg.com/three@0.152.2/examples/jsm/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background: url(https://adrien.malingrey.fr/jeux/.assets/themes/clouds/background.jpg);
|
||||
}
|
||||
.lil-gui {
|
||||
--background-color: rgba(33, 37, 41, 30%);
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
.lil-gui.autoPlace {
|
||||
left: 15px;
|
||||
}
|
||||
.lil-gui .controller.disabled {
|
||||
opacity: .8;
|
||||
}
|
||||
i {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
||||
|
||||
var game = {
|
||||
startLevel: 1,
|
||||
start: () => {
|
||||
gui.gameFolder.hide()
|
||||
gui.statsFolder.show()
|
||||
gui.statsFolder.open()
|
||||
gui.settingsFolder.close()
|
||||
},
|
||||
}
|
||||
|
||||
var stats = {
|
||||
level: 1,
|
||||
goal: 0,
|
||||
score: 0,
|
||||
highScore:0,
|
||||
time: "00:00:00",
|
||||
}
|
||||
|
||||
var settings = {
|
||||
moveLeftKey : "ArrowLeft",
|
||||
moveRightKey: "ArrowRight",
|
||||
rotateCCWKey: "w",
|
||||
rotateCWKey : "ArrowUp",
|
||||
softDropKey : "ArrowDown",
|
||||
hardDropKey : " ",
|
||||
holdKey : "c",
|
||||
pauseKey : "Escape",
|
||||
|
||||
arrDelay: 50,
|
||||
dasDelay: 300,
|
||||
|
||||
musicVolume: 50,
|
||||
sfxVolume : 50,
|
||||
};
|
||||
|
||||
const KEY_NAMES = {
|
||||
["ArrowLeft"] : "←",
|
||||
["ArrowRight"] : "→",
|
||||
["ArrowUp"] : "↑",
|
||||
["ArrowDown"] : "↓",
|
||||
[" "] : "Espace",
|
||||
["Escape"] : "Échap.",
|
||||
["Backspace"] : "Ret. arrière",
|
||||
["Enter"] : "Entrée",
|
||||
["←"] : "ArrowLeft",
|
||||
["→"] : "ArrowRight",
|
||||
["↑"] : "ArrowUp",
|
||||
["↓"] : "ArrowDown",
|
||||
["Espace"] : " ",
|
||||
["Échap."] : "Escape",
|
||||
["Ret. arrière"]: "Backspace",
|
||||
["Entrée"] : "Enter",
|
||||
}
|
||||
|
||||
function changeKey(event) {
|
||||
const input = event.target
|
||||
let prevValue = input.value
|
||||
input.value = ""
|
||||
input.onkeydown = function (event) {
|
||||
event.preventDefault()
|
||||
input.value = KEY_NAMES[event.key] || event.key
|
||||
input.blur()
|
||||
}
|
||||
input.onblur = function (event) {
|
||||
if (input.value == "") input.value = prevValue
|
||||
input.onkeydown = null
|
||||
input.onblur = null
|
||||
}
|
||||
}
|
||||
|
||||
class Gui extends GUI {
|
||||
constructor() {
|
||||
super({title: "teTra"});
|
||||
|
||||
this.gameFolder = this.addFolder("Partie")
|
||||
this.gameFolder.add(game, "startLevel").name("Niveau").min(1).max(15).step(1)
|
||||
this.gameFolder.add(game, "start").name("Commencer")
|
||||
this.gameFolder.open()
|
||||
|
||||
this.statsFolder = this.addFolder("Stats")
|
||||
this.statsFolder.add(stats,"level").name("Niveau").disable()
|
||||
this.statsFolder.add(stats,"goal").name("Objectif").disable()
|
||||
this.statsFolder.add(stats,"score").name("Score").disable()
|
||||
this.statsFolder.add(stats,"highScore").name("Meilleur score").disable()
|
||||
this.statsFolder.add(stats,"time").name("Temps").disable()
|
||||
this.statsFolder.hide()
|
||||
|
||||
this.settingsFolder = this.addFolder("Options");
|
||||
this.settingsFolder.close()
|
||||
|
||||
this.settingsFolder.keyMapping = this.settingsFolder.addFolder("Commandes")
|
||||
this.settingsFolder.keyMapping.add(settings,"moveLeftKey").name('<i class="bi bi-arrow-left"></i>').domElement.onclick = changeKey
|
||||
this.settingsFolder.keyMapping.add(settings,"moveRightKey").name('<i class="bi bi-arrow-right"></i>').domElement.onclick = changeKey
|
||||
this.settingsFolder.keyMapping.add(settings,"rotateCCWKey").name('<i class="bi bi-arrow-counterclockwise"></i>').domElement.onclick = changeKey
|
||||
this.settingsFolder.keyMapping.add(settings,"rotateCWKey").name('<i class="bi bi-arrow-clockwise"></i>').domElement.onclick = changeKey
|
||||
this.settingsFolder.keyMapping.add(settings,"softDropKey").name('<i class="bi bi-arrow-down-short"></i>').domElement.onclick = changeKey
|
||||
this.settingsFolder.keyMapping.add(settings,"hardDropKey").name('<i class="bi bi-download"></i>').domElement.onclick = changeKey
|
||||
this.settingsFolder.keyMapping.add(settings,"holdKey").name('<i class="bi bi-arrow-left-right"></i>').domElement.onclick = changeKey
|
||||
this.settingsFolder.keyMapping.add(settings,"pauseKey").name('<i class="bi bi-pause"></i>').domElement.onclick = changeKey
|
||||
this.settingsFolder.keyMapping.open()
|
||||
|
||||
this.settingsFolder.delayFolder = this.settingsFolder.addFolder("Répétition automatique")
|
||||
this.settingsFolder.delayFolder.add(settings,"arrDelay").name("ARR (ms)").min(2).max(200);
|
||||
this.settingsFolder.delayFolder.add(settings,"dasDelay").name("DAS (ms)").min(100).max(500).step(5);
|
||||
this.settingsFolder.delayFolder.open()
|
||||
|
||||
this.settingsFolder.volumeFolder = this.settingsFolder.addFolder("Volume")
|
||||
this.settingsFolder.volumeFolder.add(settings,"musicVolume").name("Musique").min(0).max(100);
|
||||
this.settingsFolder.volumeFolder.add(settings,"sfxVolume").name("SFX").min(0).max(100)
|
||||
this.settingsFolder.volumeFolder.open()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var gui = new Gui();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
148
index.html
148
index.html
@ -6,9 +6,8 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>teTra</title>
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="loading.css">
|
||||
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
|
||||
<script type="importmap">
|
||||
{
|
||||
@ -20,146 +19,17 @@
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body data-bs-theme="dark">
|
||||
|
||||
<div class="modal fade" id="settingsModal" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 id="titleHeader" class="modal-title w-100 text-center">teTra</h1>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form name="settingsForm" class="needs-validation" novalidate>
|
||||
<fieldset id="keyBindFielset" class="row g-2 mb-3 align-items-center text-center">
|
||||
<legend class="text-start">Commandes</legend>
|
||||
<label for="moveLeftInput" title="Gauche" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input name="moveLeft" id="moveLeftInput" type="text" class="form-control text-center" value="←" onclick="changeKey(this)" placeholder="Touche ?" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input name="moveRight" id="moveRightInput" type="text" class="form-control text-center" value="→" onclick="changeKey(this)" placeholder="Touche ?" required>
|
||||
</div>
|
||||
<label for="moveRightInput" title="Droite" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-arrow-right"></i>
|
||||
</label>
|
||||
<label for="rotateCounterclockwiseInput" title="Rotation anti-horaire" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input name="rotateCounterclockwise" id="rotateCounterclockwiseInput" type="text" class="form-control text-center" value="w" onclick="changeKey(this)" placeholder="Touche ?" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input name="rotateClockwise" id="rotateClockwiseInput" type="text" class="form-control text-center" value="↑" onclick="changeKey(this)" placeholder="Touche ?" required>
|
||||
</div>
|
||||
<label for="rotateClockwiseInput" title="Rotation horaire" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</label>
|
||||
<label for="softDropInput" title="Chute lente" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-arrow-down-short"></i>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input name="softDrop" id="softDropInput" type="text" class="form-control text-center" value="↓" onclick="changeKey(this)" placeholder="Touche ?" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input name="hardDrop" id="hardDropInput" type="text" class="form-control text-center" value="Espace" onclick="changeKey(this)" placeholder="Touche ?" required>
|
||||
</div>
|
||||
<label for="hardDropInput" title="Chute rapide" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-download"></i>
|
||||
</label>
|
||||
<label for="holdInput" title="Échanger la pièce" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-arrow-left-right"></i>
|
||||
</label>
|
||||
<div class="col-sm-4">
|
||||
<input name="hold" id="holdInput" type="text" class="form-control text-center" value="c" onclick="changeKey(this)" placeholder="Touche ?" required>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input name="pause" id="pauseInput" type="text" class="form-control text-center" value="Échap" onclick="changeKey(this)" placeholder="Touche ?" required>
|
||||
</div>
|
||||
<label for="pauseInput" title="Pause" class="col-sm-2 col-form-label d-flex align-items-center justify-content-center">
|
||||
<i class="bi bi-pause"></i>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset id="autorepearFieldset" class="row g-2 mb-3 align-items-center text-center">
|
||||
<legend class="text-start">Répétition automatique</legend>
|
||||
<label for="arrInput" class="col-sm-2 col-form-label" title="Automatic Repeat Rate : période de répétition de l'action">ARR</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<input name="arr" id="arrInput" type="number" class="form-control text-center" value="50" min="2" max="200" step="1">
|
||||
<div class="input-group-text">ms</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<input name="das" id="dasInput" type="number" class="form-control text-center" value="300" min="100" max="500" step="5">
|
||||
<div class="input-group-text">ms</div>
|
||||
</div>
|
||||
</div>
|
||||
<label for="dasInput" class="col-sm-2 col-form-label" title="Delayed AutoShift : délai initial avant répétition">DAS</label>
|
||||
</fieldset>
|
||||
<fieldset id="audioFieldset" class="row g-2 mb-3 align-items-center text-center">
|
||||
<legend class="text-start">Volume</legend>
|
||||
<label for="musicVolumeInput" class="col-sm-2 col-form-label">Musique</label>
|
||||
<div class="col-sm-4">
|
||||
<input name="musicVolume" id="musicVolumeInput" type="range" class="form-range" value=".5" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input name="sfxVolume" id="sfxVolumeInput" type="range" class="form-range" value=".5" min="0" max="1" step="0.01">
|
||||
</div>
|
||||
<label for="sfxVolumeInput" class="col-sm-2 col-form-label">SFX</label>
|
||||
</fieldset>
|
||||
<fieldset class="row g-2 mb-3 align-items-center text-center">
|
||||
<legend class="text-start">Partie</legend>
|
||||
<label for="levelInput" class="col-sm-2 col-form-label text-center">Niveau</label>
|
||||
<div class="col-sm-4">
|
||||
<input name="startLevel" id="levelInput" type="number" class="form-control text-center" value="1" min="1" max="15">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<button id="resumeButton" type="submit" class="btn btn-primary w-100" autofocus>Jouer</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span id="messagesSpan">
|
||||
<div class="progress" role="progressbar">
|
||||
<div id="loadingBar" class="progress-bar overflow-visible progress-bar-striped progress-bar-animated" style="width: 0%">Chargement...</div>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span id="scoreSpan">Score<br/><div id="scoreDiv">0</div></span>
|
||||
<span id="timeSpan">Temps<br/><div id="timeDiv">00:00:00</div></span>
|
||||
<span id="levelSpan">Niveau<br/><div id="levelDiv">0</div></span>
|
||||
<span id="goalSpan">Objectif<br/><div id="goalDiv">0</div></span>
|
||||
|
||||
<div class="modal fade" id="statsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title w-100 text-center">Fin</h2>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-0">
|
||||
<table class="table mb-0">
|
||||
<tr><th>Score </th><td id="statsModalScoreCell"> </td><th>Niveau </th><td id="statsModalLevelCell"> </td></tr>
|
||||
<tr><th>Meilleur score</th><td id="statsModalHighScoreCell"> </td><th>Temps </th><td id="statsModalTimeCell"> </td></tr>
|
||||
<tr><th>Lignes </th><td id="statsModaltotalClearedLines"></td><th>Lignes par minute </th><td id="statsModaltotalClearedLinesPM"></td></tr>
|
||||
<tr><th>Quatris </th><td id="statsModalNbQuatris"> </td><th>Plus long combo </th><td id="statsModalMaxCombo"> </td></tr>
|
||||
<tr><th>Pirouettes </th><td id="statsModalNbTSpin"> </td><th>Plus long bout à bout</th><td id="statsModalMaxB2B"> </td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="restartButton" type="button" class="btn btn-primary" onclick="restart()"">Rejouer ?</button>
|
||||
</div>
|
||||
</div>
|
||||
<body>
|
||||
<div id="loaddingCircle">
|
||||
<div class="e-loadholder">
|
||||
<div class="m-loader">
|
||||
<span class="e-text">
|
||||
<div>Chargement</div>
|
||||
<div id="loadingPercent">0%</div></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span id="messagesSpan"></span>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe" crossorigin="anonymous"></script>
|
||||
<script type="module" src="app.js"></script>
|
||||
</body>
|
||||
|
274
loading.css
Normal file
274
loading.css
Normal file
@ -0,0 +1,274 @@
|
||||
@-webkit-keyframes outerRotate1 {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes outerRotate1 {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes outerRotate1 {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes outerRotate1 {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes outerRotate2 {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes outerRotate2 {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes outerRotate2 {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes outerRotate2 {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes textColour {
|
||||
0% {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: #3BB2D0;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes textColour {
|
||||
0% {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: #3BB2D0;
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes textColour {
|
||||
0% {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: #3BB2D0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes textColour {
|
||||
0% {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: #3BB2D0;
|
||||
}
|
||||
}
|
||||
|
||||
#loaddingCircle {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.e-loadholder {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-51%, -50%);
|
||||
-moz-transform: translate(-51%, -50%);
|
||||
-ms-transform: translate(-51%, -50%);
|
||||
-o-transform: translate(-51%, -50%);
|
||||
transform: translate(-51%, -50%);
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
border: 5px solid #1B5F70;
|
||||
border-radius: 120px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.e-loadholder:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-51%, -50%);
|
||||
-moz-transform: translate(-51%, -50%);
|
||||
-ms-transform: translate(-51%, -50%);
|
||||
-o-transform: translate(-51%, -50%);
|
||||
transform: translate(-51%, -50%);
|
||||
content: " ";
|
||||
display: block;
|
||||
background: #222;
|
||||
transform-origin: center;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.e-loadholder:after {
|
||||
width: 100px;
|
||||
height: 200%;
|
||||
-webkit-animation: outerRotate2 30s infinite linear;
|
||||
-moz-animation: outerRotate2 30s infinite linear;
|
||||
-o-animation: outerRotate2 30s infinite linear;
|
||||
animation: outerRotate2 30s infinite linear;
|
||||
}
|
||||
|
||||
.e-loadholder .m-loader {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-51%, -50%);
|
||||
-moz-transform: translate(-51%, -50%);
|
||||
-ms-transform: translate(-51%, -50%);
|
||||
-o-transform: translate(-51%, -50%);
|
||||
transform: translate(-51%, -50%);
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
border: 5px solid #2a93ae;
|
||||
border-radius: 100px;
|
||||
box-sizing: border-box;
|
||||
z-index: 20;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.e-loadholder .m-loader:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-51%, -50%);
|
||||
-moz-transform: translate(-51%, -50%);
|
||||
-ms-transform: translate(-51%, -50%);
|
||||
-o-transform: translate(-51%, -50%);
|
||||
transform: translate(-51%, -50%);
|
||||
content: " ";
|
||||
display: block;
|
||||
background: #222;
|
||||
transform-origin: center;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.e-loadholder .m-loader:after {
|
||||
width: 100px;
|
||||
height: 106%;
|
||||
-webkit-animation: outerRotate1 15s infinite linear;
|
||||
-moz-animation: outerRotate1 15s infinite linear;
|
||||
-o-animation: outerRotate1 15s infinite linear;
|
||||
animation: outerRotate1 15s infinite linear;
|
||||
}
|
||||
|
||||
.e-loadholder .m-loader .e-text {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
font-size: 10px;
|
||||
font-size: 1rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-51%, -50%);
|
||||
-moz-transform: translate(-51%, -50%);
|
||||
-ms-transform: translate(-51%, -50%);
|
||||
-o-transform: translate(-51%, -50%);
|
||||
transform: translate(-51%, -50%);
|
||||
-webkit-animation: textColour 1s alternate linear infinite;
|
||||
-moz-animation: textColour 1s alternate linear infinite;
|
||||
-o-animation: textColour 1s alternate linear infinite;
|
||||
animation: textColour 1s alternate linear infinite;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
text-align: center;
|
||||
border: 5px solid #3bb2d0;
|
||||
border-radius: 70px;
|
||||
box-sizing: border-box;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.e-loadholder .m-loader .e-text:before, .e-loadholder .m-loader .e-text:after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
-webkit-transform: translate(-51%, -50%);
|
||||
-moz-transform: translate(-51%, -50%);
|
||||
-ms-transform: translate(-51%, -50%);
|
||||
-o-transform: translate(-51%, -50%);
|
||||
transform: translate(-51%, -50%);
|
||||
content: " ";
|
||||
display: block;
|
||||
background: #222;
|
||||
transform-origin: center;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.e-loadholder .m-loader .e-text:before {
|
||||
width: 110%;
|
||||
height: 40px;
|
||||
-webkit-animation: outerRotate2 3.5s infinite linear;
|
||||
-moz-animation: outerRotate2 3.5s infinite linear;
|
||||
-o-animation: outerRotate2 3.5s infinite linear;
|
||||
animation: outerRotate2 3.5s infinite linear;
|
||||
}
|
||||
|
||||
.e-loadholder .m-loader .e-text:after {
|
||||
width: 40px;
|
||||
height: 110%;
|
||||
-webkit-animation: outerRotate1 8s infinite linear;
|
||||
-moz-animation: outerRotate1 8s infinite linear;
|
||||
-o-animation: outerRotate1 8s infinite linear;
|
||||
animation: outerRotate1 8s infinite linear;
|
||||
}
|
67
style.css
67
style.css
@ -1,58 +1,34 @@
|
||||
body {
|
||||
margin: 0
|
||||
margin: 0;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#messagesSpan .progress {
|
||||
margin-top: 70vh;
|
||||
opacity: 1;
|
||||
.lil-gui {
|
||||
--background-color: rgba(33, 37, 41, 30%);
|
||||
--width: 200px;
|
||||
}
|
||||
|
||||
#messagesSpan .progress-bar {
|
||||
opacity: inherit;
|
||||
}
|
||||
|
||||
#scoreSpan {
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#timeSpan {
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#levelSpan {
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#goalSpan {
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@supports (backdrop-filter: blur()) {
|
||||
.card,
|
||||
.modal-content {
|
||||
background-color: rgba(33, 37, 41, 30%);
|
||||
.lil-gui {
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
}
|
||||
|
||||
canvas {
|
||||
cursor: grab;
|
||||
.lil-gui.autoPlace {
|
||||
top: inherit;
|
||||
bottom: 15px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
#titleHeader {
|
||||
letter-spacing: 1rem;
|
||||
.lil-gui .controller.disabled {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
canvas {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
#messagesSpan {
|
||||
@ -63,7 +39,12 @@ canvas {
|
||||
transform: translate(-50%, 0);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-shadow: 1px 1px rgba(0, 0, 0, 0.8);
|
||||
font-size: 4vmin;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
"Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
font-size: 3vmin;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -72,6 +53,10 @@ canvas {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
|
||||
@keyframes show-level-animation {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
Loading…
x
Reference in New Issue
Block a user