Compare commits

...

7 Commits

Author SHA1 Message Date
adrien a4f7ba0e08 spinend sound 2026-03-27 08:32:43 +01:00
adrien 81c49ed4ae countdown 2026-03-27 01:22:08 +01:00
adrien 18d302732c pause on fullscreen exit 2026-03-26 20:58:56 +01:00
adrien 27978c2b32 tweaks 2026-03-24 21:29:43 +01:00
adrien fc5ff692b8 move sounds 2026-03-24 08:56:19 +01:00
adrien 21585bad7a tweaks & dont speak clear lines 2026-03-24 01:50:44 +01:00
adrien 53b2361e2b merge space and retro themes 2026-03-24 01:29:44 +01:00
12 changed files with 142 additions and 115 deletions
+44 -29
View File
@@ -46,25 +46,31 @@ let game = {
}, },
resume: function() { resume: function() {
document.onkeydown = onkeydown pauseSpan.innerHTML = "3"
document.onkeyup = onkeyup setTimeout(() => pauseSpan.innerHTML = "2", 1000)
window.onblur = game.pause setTimeout(() => pauseSpan.innerHTML = "1", 2000)
menu.settings.domElement.onclick = game.pause setTimeout(() => {
pauseSpan.innerHTML = ""
document.body.classList.remove("pause") document.onkeydown = onkeydown
menu.resumeButton.hide() document.onkeyup = onkeyup
menu.pauseButton.show() window.onblur = game.pause
menu.settings.domElement.onclick = game.pause
stats.clock.start()
stats.clock.elapsedTime = stats.elapsedTime document.body.classList.remove("pause")
menu.resumeButton.hide()
if (settings.musicVolume) scene.music.play() menu.pauseButton.show()
if (scene.playfield.piece) { stats.clock.start()
scheduler.resetInterval(game.fall, stats.fallPeriod) stats.clock.elapsedTime = stats.elapsedTime
} else {
this.generate() if (settings.musicVolume) scene.music.play()
}
if (scene.playfield.piece) {
scheduler.resetInterval(game.fall, stats.fallPeriod)
} else {
this.generate()
}
}, 3000)
}, },
generate: function(nextPiece=nextQueue.shift()) { generate: function(nextPiece=nextQueue.shift()) {
@@ -122,6 +128,7 @@ let game = {
window.onblur = null window.onblur = null
pauseSpan.onfocus = game.resume pauseSpan.onfocus = game.resume
pauseSpan.innerHTML = "II"
document.body.classList.add("pause") document.body.classList.add("pause")
menu.pauseButton.hide() menu.pauseButton.hide()
menu.resumeButton.show() menu.resumeButton.show()
@@ -148,19 +155,20 @@ let game = {
fullscreen: function() { fullscreen: function() {
if (document.fullscreenElement) { if (document.fullscreenElement) {
if (document.exitFullscreen) { if (document.exitFullscreen) {
document.exitFullscreen(); document.exitFullscreen()
} }
} else { } else {
document.body.requestFullscreen(); document.body.requestFullscreen()
} }
} }
} }
document.onfullscreenchange = function() { document.onfullscreenchange = function() {
if (document.fullscreenElement) { if (document.fullscreenElement) {
menu.settings.fullscreenButton.name("Quitter le plein écran"); menu.settings.fullscreenButton.name("Quitter le plein écran")
} else { } else {
menu.settings.fullscreenButton.name("Plein écran"); menu.settings.fullscreenButton.name("Plein écran")
game.pause()
} }
} }
@@ -175,16 +183,23 @@ function playSound(sound, note=0) {
/* Handle player inputs */ /* Handle player inputs */
let playerActions = { let playerActions = {
moveLeft: () => scene.playfield.piece.move(TRANSLATION.LEFT), moveLeft: () => scene.playfield.piece.move(TRANSLATION.LEFT)? scene.moveSound.play() : scene.hitSound.stop() && scene.hitSound.play(),
moveRight: () => scene.playfield.piece.move(TRANSLATION.RIGHT), moveRight: () => scene.playfield.piece.move(TRANSLATION.RIGHT)? scene.moveSound.play() : scene.hitSound.stop() && scene.hitSound.play(),
rotateCW: () => scene.playfield.piece.rotate(ROTATION.CW), rotateCW: () => scene.playfield.piece.rotate(ROTATION.CW)? scene.rotateSound.stop() && scene.rotateSound.play() : scene.spinEndSound.stop() && scene.spinEndSound.play(),
rotateCCW: () => scene.playfield.piece.rotate(ROTATION.CCW), rotateCCW: () => scene.playfield.piece.rotate(ROTATION.CCW)? scene.rotateSound.stop() && scene.rotateSound.play() : scene.spinEndSound.stop() && scene.spinEndSound.play(),
softDrop: function () { softDrop: function () {
if (scene.playfield.piece.move(TRANSLATION.DOWN)) stats.score++ if (scene.playfield.piece.move(TRANSLATION.DOWN)) {
stats.score++
scene.moveSound.stop()
scene.moveSound.play()
} else {
scene.floorSound.stop()
scene.floorSound.play()
}
}, },
hardDrop: function () { hardDrop: function () {
@@ -285,7 +300,7 @@ function resumeOnKeyDown(event) {
let loadingManager = new THREE.LoadingManager( let loadingManager = new THREE.LoadingManager(
function() { function() {
loadingDiv.style.display = "none" loadingDiv.style.display = "none"
menu.startButton.show() if (!game.playing) menu.startButton.show()
scene.renderer.setAnimationLoop(animate) scene.renderer.setAnimationLoop(animate)
}, },
function (url, itemsLoaded, itemsTotal) { function (url, itemsLoaded, itemsTotal) {
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -28,7 +28,7 @@
</script> </script>
</head> </head>
<body> <body class="pause">
<span id="loadingDiv"> <span id="loadingDiv">
<div class="scene"> <div class="scene">
<div class="T tetromino"> <div class="T tetromino">
@@ -72,7 +72,7 @@
</div> </div>
</span> </span>
<span id="messagesSpan"></span> <span id="messagesSpan"></span>
<span id="pauseSpan" tabindex="1">II</span> <span id="pauseSpan" tabindex="1"></span>
<audio id="music" src="audio/benevolence.m4a" loop></audio> <audio id="music" src="audio/benevolence.m4a" loop></audio>
<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 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> <script type="module" src="app.js"></script>
+2 -1
View File
@@ -26,7 +26,7 @@ export class Menu extends GUI {
this.settings = this.addFolder("Options") this.settings = this.addFolder("Options")
this.settings.add(settings, "startLevel").name("Niveau initial").min(1).max(15).step(1) this.settings.add(settings, "startLevel").name("Niveau initial").min(1).max(15).step(1)
this.settings.add(settings, "theme", ["Plasma", "Space", "Retro"]).name("Thème").onChange(theme => { this.settings.add(settings, "theme", ["Plasma", "Space"]).name("Thème").onChange(theme => {
scene.theme = theme scene.theme = theme
if (dev) changeMaterial() if (dev) changeMaterial()
}) })
@@ -111,6 +111,7 @@ export class Menu extends GUI {
}) })
let minoMaterial = scene.minoes.material instanceof Array ? scene.minoes.material[0] : scene.minoes.material let minoMaterial = scene.minoes.material instanceof Array ? scene.minoes.material[0] : scene.minoes.material
if ("color" in minoMaterial) material.addColor(minoMaterial, "color" )
if ("opacity" in minoMaterial) material.add(minoMaterial, "opacity" ).min(0).max(1) if ("opacity" in minoMaterial) material.add(minoMaterial, "opacity" ).min(0).max(1)
if ("reflectivity" in minoMaterial) material.add(minoMaterial, "reflectivity" ).min(0).max(1) if ("reflectivity" in minoMaterial) material.add(minoMaterial, "reflectivity" ).min(0).max(1)
if ("roughness" in minoMaterial) material.add(minoMaterial, "roughness" ).min(0).max(1) if ("roughness" in minoMaterial) material.add(minoMaterial, "roughness" ).min(0).max(1)
+2 -2
View File
@@ -181,10 +181,10 @@ class Stats {
this.b2b = -1 this.b2b = -1
} }
if (this.speechSynthesisAvailable && this.settings.sfxVolume) { /*if (this.speechSynthesisAvailable && this.settings.sfxVolume) {
if (tSpin) this.speak(tSpin, this.settings.sfxVolume); if (tSpin) this.speak(tSpin, this.settings.sfxVolume);
if (nbClearedLines > 1) this.speak(CLEARED_LINES_NAMES[nbClearedLines], this.settings.sfxVolume); if (nbClearedLines > 1) this.speak(CLEARED_LINES_NAMES[nbClearedLines], this.settings.sfxVolume);
} }*/
this.goal -= awardedLineClears this.goal -= awardedLineClears
if (this.goal <= 0) return this.level++ if (this.goal <= 0) return this.level++
+31 -16
View File
@@ -47,17 +47,42 @@ export class TetraScene extends THREE.Scene {
this.lineClearSound = new THREE.Audio(listener) this.lineClearSound = new THREE.Audio(listener)
audioLoader.load('audio/line-clear.ogg', function( buffer ) { audioLoader.load('audio/line-clear.ogg', function( buffer ) {
this.lineClearSound.setBuffer(buffer) this.lineClearSound.setBuffer(buffer)
this.lineClearSound.setVolume(settings.sfxVolume/100)
}.bind(this)) }.bind(this))
this.tetrisSound = new THREE.Audio(listener) this.tetrisSound = new THREE.Audio(listener)
audioLoader.load('audio/tetris.ogg', function( buffer ) { audioLoader.load('audio/tetris.ogg', function( buffer ) {
this.tetrisSound.setBuffer(buffer) this.tetrisSound.setBuffer(buffer)
this.lineClearSound.setVolume(settings.sfxVolume/100)
this.tetrisSound.setVolume(settings.sfxVolume/100) this.tetrisSound.setVolume(settings.sfxVolume/100)
this.hardDropSound.setVolume(settings.sfxVolume/100)
}.bind(this)) }.bind(this))
this.hardDropSound = new THREE.Audio(listener) this.hardDropSound = new THREE.Audio(listener)
audioLoader.load('audio/hard-drop.wav', function( buffer ) { audioLoader.load('audio/hard-drop.wav', function( buffer ) {
this.hardDropSound.setBuffer(buffer) this.hardDropSound.setBuffer(buffer)
this.hardDropSound.setVolume(settings.sfxVolume/100)
}.bind(this))
this.hitSound = new THREE.Audio(listener)
audioLoader.load('audio/hit.mp3', function( buffer ) {
this.hitSound.setBuffer(buffer)
this.hitSound.setVolume(settings.sfxVolume/100)
}.bind(this))
this.floorSound = new THREE.Audio(listener)
audioLoader.load('audio/floor.ogg', function( buffer ) {
this.floorSound.setBuffer(buffer)
this.floorSound.setVolume(settings.sfxVolume/100)
}.bind(this))
this.moveSound = new THREE.Audio(listener)
audioLoader.load('audio/move.ogg', function( buffer ) {
this.moveSound.setBuffer(buffer)
this.moveSound.setVolume(settings.sfxVolume/100)
}.bind(this))
this.rotateSound = new THREE.Audio(listener)
audioLoader.load('audio/rotate.ogg', function( buffer ) {
this.rotateSound.setBuffer(buffer)
this.rotateSound.setVolume(settings.sfxVolume/100)
}.bind(this))
this.spinEndSound = new THREE.Audio(listener)
audioLoader.load('audio/spinend.mp3', function( buffer ) {
this.spinEndSound.setBuffer(buffer)
this.spinEndSound.setVolume(settings.sfxVolume/100)
}.bind(this)) }.bind(this))
this.playfield = new Playfield(loadingManager) this.playfield = new Playfield(loadingManager)
@@ -71,9 +96,9 @@ export class TetraScene extends THREE.Scene {
set theme(theme) { set theme(theme) {
switch (theme) { switch (theme) {
case "Plasma": case "Plasma":
this.ambientLight.intensity = 0 this.ambientLight.intensity = 1
this.directionalLight.intensity = 3 this.directionalLight.intensity = 3
this.directionalLight.position.set(5, -20, 20) this.directionalLight.position.set(5, 50, -20)
this.music.src = "audio/Moon-Over-Moscow-DJ-ResiDance-Mix-2022.mp3" this.music.src = "audio/Moon-Over-Moscow-DJ-ResiDance-Mix-2022.mp3"
this.background = new THREE.Color(0xffffff) this.background = new THREE.Color(0xffffff)
this.fog.color.set(0xffffff) this.fog.color.set(0xffffff)
@@ -81,22 +106,12 @@ export class TetraScene extends THREE.Scene {
this.playfield.retroEdge.visible = false this.playfield.retroEdge.visible = false
break break
case "Space": case "Space":
this.ambientLight.intensity = 5 this.ambientLight.intensity = 3
this.directionalLight.intensity = 10 this.directionalLight.intensity = 10
this.directionalLight.position.set(2, 15, 20) this.directionalLight.position.set(2, 15, -20)
this.music.src = "audio/benevolence.m4a" this.music.src = "audio/benevolence.m4a"
this.background = new THREE.Color(0x000000) this.background = new THREE.Color(0x000000)
this.fog.color.set(0x000000) this.fog.color.set(0x000000)
this.playfield.edge.visible = true
this.playfield.retroEdge.visible = false
break
case "Retro":
this.ambientLight.intensity = 1
this.directionalLight.intensity = 10
this.directionalLight.position.set(19, 120, 200)
this.music.src = "audio/Tetris_MkVaffQuasi_Ultimix_OC_ReMix.mp3"
this.background = new THREE.Color(0x000000)
this.fog.color.set(0x000000)
this.playfield.edge.visible = false this.playfield.edge.visible = false
this.playfield.retroEdge.visible = true this.playfield.retroEdge.visible = true
break break
+45 -44
View File
@@ -79,7 +79,7 @@ export class InstancedMino extends THREE.InstancedMesh {
} }
set theme(theme) { set theme(theme) {
if (theme == "Retro") { if (theme == "Space") {
this.resetColor() this.resetColor()
this.update = this.updateOffset this.update = this.updateOffset
if (this.materials["Retro"]) { if (this.materials["Retro"]) {
@@ -93,9 +93,11 @@ export class InstancedMino extends THREE.InstancedMesh {
color: COLORS.RETRO, color: COLORS.RETRO,
map: texture, map: texture,
bumpMap: texture, bumpMap: texture,
bumpScale: 1.5, bumpScale: 1,
roughness: 0.25, envMap: environment,
metalness: 0.9, envMapIntensity: 5,
roughness: 0.03,
metalness: 1,
transparent: true, transparent: true,
}, 8, 8) }, 8, 8)
}) })
@@ -107,9 +109,11 @@ export class InstancedMino extends THREE.InstancedMesh {
color: COLORS.RETRO, color: COLORS.RETRO,
map: texture, map: texture,
bumpMap: texture, bumpMap: texture,
bumpScale: 1.5, bumpScale: 1,
roughness: 0.25, envMap: environment,
metalness: 0.9, envMapIntensity: 5,
roughness: 0.03,
metalness: 1,
transparent: true, transparent: true,
}, 1, 1) }, 1, 1)
}) })
@@ -164,35 +168,26 @@ InstancedMino.prototype.materials = {
envMap: environment, envMap: environment,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, transparent: true,
opacity: 0.55, opacity: 0.66,
roughness: 0.5, roughness: 0.1,
metalness: 0.9, metalness: 0.6,
}), onBeforeCompile: shader => {
Space: new THREE.MeshStandardMaterial({ shader.vertexShader = `
envMap: environment, varying vec3 vPos;
side: THREE.DoubleSide, ${shader.vertexShader}
transparent: true, `.replace(
opacity: 0.66, '#include <begin_vertex>',
roughness: 0.01, `
metalness: 0.99, #include <begin_vertex>
vPos = position;
float n = sin(position.x*3.1 + position.y*5.2 + position.z*7.3) * 0.03;
transformed += normal * n;
`
);
}
}) })
} }
InstancedMino.prototype.materials['Plasma'].onBeforeCompile = shader => {
shader.vertexShader = `
varying vec3 vPos;
${shader.vertexShader}
`.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
vPos = position;
// Bruit basé sur la position du sommet pour irrégularité
float n = sin(position.x*3.1 + position.y*5.2 + position.z*7.3) * 0.03;
transformed += normal * n;
`
);
};
class Mino extends THREE.Object3D { class Mino extends THREE.Object3D {
@@ -483,8 +478,8 @@ class Playfield extends THREE.Group {
.lineTo(COLUMNS, 0) .lineTo(COLUMNS, 0)
.lineTo(COLUMNS, SKYLINE) .lineTo(COLUMNS, SKYLINE)
.lineTo(COLUMNS + 1, SKYLINE) .lineTo(COLUMNS + 1, SKYLINE)
.lineTo(COLUMNS + 1, -1/3) .lineTo(COLUMNS + 1, -6/8)
.lineTo(-1, -1/3) .lineTo(-1, -6/8)
.moveTo(-1, SKYLINE) .moveTo(-1, SKYLINE)
const retroEdgeTexture = new THREE.TextureLoader(loadingManager).load("images/edge.png", (texture) => { const retroEdgeTexture = new THREE.TextureLoader(loadingManager).load("images/edge.png", (texture) => {
texture.wrapS = THREE.RepeatWrapping texture.wrapS = THREE.RepeatWrapping
@@ -494,9 +489,11 @@ class Playfield extends THREE.Group {
color: COLORS.RETRO, color: COLORS.RETRO,
map: retroEdgeTexture, map: retroEdgeTexture,
bumpMap: retroEdgeTexture, bumpMap: retroEdgeTexture,
bumpScale: 1.5, bumpScale: 1,
roughness: 0.25, envMap: environment,
metalness: 0.8, envMapIntensity: 5,
roughness: 0.03,
metalness: 1
}) })
this.retroEdge = new THREE.Mesh( this.retroEdge = new THREE.Mesh(
new THREE.ExtrudeGeometry(retroEdgeShape, { new THREE.ExtrudeGeometry(retroEdgeShape, {
@@ -506,11 +503,15 @@ class Playfield extends THREE.Group {
[retroEdgeMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial], [retroEdgeMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial, sideMaterial],
) )
const back = new THREE.Mesh( const back = new THREE.Mesh(
new THREE.PlaneGeometry(COLUMNS, SKYLINE), new THREE.PlaneGeometry(COLUMNS, SKYLINE),
new THREE.MeshStandardMaterial({ new THREE.MeshStandardMaterial({
color: 0xc5d0a1, color: 0xc5d0a1,
roughness: 0.1, envMap: environment,
metalness: 0.9, envMapIntensity: 5,
roughness: 0.1,
metalness: 0.9,
opacity: 0.1,
transparent: true
}) })
) )
back.position.set(COLUMNS/2, SKYLINE/2) back.position.set(COLUMNS/2, SKYLINE/2)
+16 -21
View File
@@ -17,7 +17,7 @@ export class Vortex extends THREE.Group {
this.opaqueCylinder = new THREE.Mesh( this.opaqueCylinder = new THREE.Mesh(
new THREE.CylinderGeometry(40, 40, 1000, 12, 1, true), new THREE.CylinderGeometry(40, 40, 1000, 12, 1, true),
new THREE.MeshBasicMaterial({ new THREE.MeshLambertMaterial({
side: THREE.BackSide, side: THREE.BackSide,
blending: THREE.MultiplyBlending blending: THREE.MultiplyBlending
}) })
@@ -44,7 +44,7 @@ export class Vortex extends THREE.Group {
texture.wrapT = THREE.MirroredRepeatWrapping texture.wrapT = THREE.MirroredRepeatWrapping
texture.repeat.set(1, 2) texture.repeat.set(1, 2)
this.transparentCylinder.material.map = texture this.transparentCylinder.material.map = texture
this.transparentCylinder.material.opacity = 0.15 this.transparentCylinder.material.opacity = 0.3
this.add(this.transparentCylinder) this.add(this.transparentCylinder)
}) })
@@ -57,48 +57,43 @@ export class Vortex extends THREE.Group {
this.add(this.opaqueCylinder) this.add(this.opaqueCylinder)
}) })
this.globalRotation = 0.028 this.globalRotation = 0.028
this.transparentTextureRotation = 0.005 this.transparentTextureRotation = 0.005
this.transparentMoveForward = 0.009 this.transparentMoveForward = 0.009
this.opaqueTextureRotation = 0.006 this.opaqueTextureRotation = 0.006
this.opaqueMoveForward = 0.025 this.opaqueMoveForward = 0.025
this.visible = true this.visible = true
break break
case "Space": case "Space":
loader.load("./images/colorfull.jpg", texture => { loader.load("./images/colorfull.jpg", texture => {
texture.wrapS = THREE.RepeatWrapping texture.wrapS = THREE.MirroredRepeatWrapping
texture.wrapT = THREE.MirroredRepeatWrapping texture.wrapT = THREE.MirroredRepeatWrapping
texture.repeat.set(1, 4) texture.repeat.set(1, 8)
this.transparentCylinder.material.map = texture this.transparentCylinder.material.map = texture
this.transparentCylinder.material.opacity = 0.3 this.transparentCylinder.material.opacity = 0.3
this.add(this.transparentCylinder) this.add(this.transparentCylinder)
}) })
loader.load("./images/stars_space.jpg", texture => { loader.load("./images/stars_space.jpg", texture => {
texture.wrapS = THREE.RepeatWrapping texture.wrapS = THREE.MirroredRepeatWrapping
texture.wrapT = THREE.RepeatWrapping texture.wrapT = THREE.MirroredRepeatWrapping
texture.repeat.set(3, 6) texture.repeat.set(3, 6)
this.opaqueCylinder.material.map = texture this.opaqueCylinder.material.map = texture
this.opaqueCylinder.material.map.offset.x = 5
this.opaqueCylinder.material.blending = THREE.AdditiveBlending this.opaqueCylinder.material.blending = THREE.AdditiveBlending
this.add(this.opaqueCylinder) this.add(this.opaqueCylinder)
}) })
this.globalRotation = 0.028 this.globalRotation = 0.028
this.opaqueTextureRotation = 0.006 this.opaqueTextureRotation = 0.006
this.opaqueMoveForward = 0.06 this.opaqueMoveForward = 0.04
this.transparentTextureRotation = 0 this.transparentTextureRotation = 0
this.transparentMoveForward = 0.01 this.transparentMoveForward = 0.02
this.visible = true this.visible = true
break break
case "Retro":
this.remove(this.transparentCylinder)
this.remove(this.opaqueCylinder)
this.visible = false
break
} }
} }