refactoring
This commit is contained in:
parent
058fdd8f8b
commit
a6c48989d0
663
app.js
663
app.js
@ -1,12 +1,12 @@
|
|||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import { T_SPIN } from './jsm/common.js'
|
import { scheduler } from './jsm/scheduler.js'
|
||||||
|
import { TRANSLATION, ROTATION, environnement, Matrix, HoldQueue, NextQueue } from './jsm/gamelogic.js'
|
||||||
import { Settings } from './jsm/Settings.js'
|
import { Settings } from './jsm/Settings.js'
|
||||||
import { Stats } from './jsm/Stats.js'
|
import { Stats } from './jsm/Stats.js'
|
||||||
import { Scheduler } from './jsm/utils.js'
|
|
||||||
import { TetraGUI } from './jsm/TetraGUI.js'
|
import { TetraGUI } from './jsm/TetraGUI.js'
|
||||||
import { TetraControls } from './jsm/TetraControls.js'
|
import { TetraControls } from './jsm/TetraControls.js'
|
||||||
|
import { Vortex } from './jsm/Vortex.js'
|
||||||
|
|
||||||
let P = (x, y, z = 0) => new THREE.Vector3(x, y, z)
|
|
||||||
|
|
||||||
Array.prototype.pick = function () { return this.splice(Math.floor(Math.random() * this.length), 1)[0] }
|
Array.prototype.pick = function () { return this.splice(Math.floor(Math.random() * this.length), 1)[0] }
|
||||||
|
|
||||||
@ -19,439 +19,7 @@ HTMLElement.prototype.addNewChild = function (tag, properties) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Constants */
|
/* Scene */
|
||||||
|
|
||||||
const ROWS = 24
|
|
||||||
const SKYLINE = 20
|
|
||||||
const COLUMNS = 10
|
|
||||||
|
|
||||||
const COLORS = {
|
|
||||||
I: 0xafeff9,
|
|
||||||
J: 0xb8b4ff,
|
|
||||||
L: 0xfdd0b7,
|
|
||||||
O: 0xffedac,
|
|
||||||
S: 0xC8FBA8,
|
|
||||||
T: 0xedb2ff,
|
|
||||||
Z: 0xffb8c5,
|
|
||||||
}
|
|
||||||
|
|
||||||
const FACING = {
|
|
||||||
NORTH: 0,
|
|
||||||
EAST: 1,
|
|
||||||
SOUTH: 2,
|
|
||||||
WEST: 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
const TRANSLATION = {
|
|
||||||
NONE : P( 0, 0),
|
|
||||||
LEFT : P(-1, 0),
|
|
||||||
RIGHT: P( 1, 0),
|
|
||||||
UP : P( 0, 1),
|
|
||||||
DOWN : P( 0, -1),
|
|
||||||
}
|
|
||||||
|
|
||||||
const ROTATION = {
|
|
||||||
CW: 1, // ClockWise
|
|
||||||
CCW: 3, // CounterClockWise
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Matrix extends THREE.Group {
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
|
|
||||||
const edgeMaterial = new THREE.MeshBasicMaterial({
|
|
||||||
color: 0x88abe0,
|
|
||||||
envMap: envRenderTarget.texture,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.4,
|
|
||||||
reflectivity: 0.9,
|
|
||||||
refractionRatio: 0.5
|
|
||||||
})
|
|
||||||
|
|
||||||
const edgeShape = new THREE.Shape()
|
|
||||||
edgeShape.moveTo(-.3, SKYLINE)
|
|
||||||
edgeShape.lineTo(0, SKYLINE)
|
|
||||||
edgeShape.lineTo(0, 0)
|
|
||||||
edgeShape.lineTo(COLUMNS, 0)
|
|
||||||
edgeShape.lineTo(COLUMNS, SKYLINE)
|
|
||||||
edgeShape.lineTo(COLUMNS + .3, SKYLINE)
|
|
||||||
edgeShape.lineTo(COLUMNS + .3, -.3)
|
|
||||||
edgeShape.lineTo(-.3, -.3)
|
|
||||||
edgeShape.moveTo(-.3, SKYLINE)
|
|
||||||
this.edge = new THREE.Mesh(
|
|
||||||
new THREE.ExtrudeGeometry(edgeShape, {
|
|
||||||
depth: 1,
|
|
||||||
bevelEnabled: false,
|
|
||||||
}),
|
|
||||||
edgeMaterial
|
|
||||||
)
|
|
||||||
this.edge.visible = false
|
|
||||||
|
|
||||||
const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1, 2], [0, 0, 0, 0, -0.2, 0, 0, 0, 0])
|
|
||||||
const clip = new THREE.AnimationClip('HardDrop', 3, [positionKF])
|
|
||||||
const animationGroup = new THREE.AnimationObjectGroup()
|
|
||||||
animationGroup.add(this)
|
|
||||||
animationGroup.add(this.edge)
|
|
||||||
this.mixer = new THREE.AnimationMixer(animationGroup)
|
|
||||||
const hardDroppedMatrix = this.mixer.clipAction(clip)
|
|
||||||
hardDroppedMatrix.loop = THREE.LoopOnce
|
|
||||||
hardDroppedMatrix.setDuration(0.2)
|
|
||||||
|
|
||||||
this.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.cells = Array(ROWS).fill().map(() => Array(COLUMNS))
|
|
||||||
this.unlockedMinoes = new Set()
|
|
||||||
}
|
|
||||||
|
|
||||||
cellIsEmpty(p) {
|
|
||||||
return 0 <= p.x && p.x < COLUMNS &&
|
|
||||||
0 <= p.y && p.y < ROWS &&
|
|
||||||
!this.cells[p.y][p.x]
|
|
||||||
}
|
|
||||||
|
|
||||||
lock() {
|
|
||||||
this.piece.locking = false
|
|
||||||
let minoes = Array.from(this.piece.children)
|
|
||||||
minoes.forEach(mino => {
|
|
||||||
mino.position.add(this.piece.position)
|
|
||||||
this.add(mino)
|
|
||||||
if (this.cellIsEmpty(mino.position)) {
|
|
||||||
this.cells[mino.position.y][mino.position.x] = mino
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return minoes.some(mino => mino.position.y < SKYLINE)
|
|
||||||
}
|
|
||||||
|
|
||||||
clearLines() {
|
|
||||||
let nbClearedLines = this.cells.reduceRight((nbClearedLines, row, y) => {
|
|
||||||
if (row.filter(mino => mino).length == COLUMNS) {
|
|
||||||
row.forEach(mino => this.unlockedMinoes.add(mino))
|
|
||||||
this.cells.splice(y, 1)
|
|
||||||
this.cells.push(Array(COLUMNS))
|
|
||||||
return ++nbClearedLines
|
|
||||||
}
|
|
||||||
return nbClearedLines
|
|
||||||
}, 0)
|
|
||||||
if (nbClearedLines) {
|
|
||||||
this.cells.forEach((rows, y) => {
|
|
||||||
rows.forEach((mino, x) => {
|
|
||||||
mino.position.set(x, y)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nbClearedLines
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUnlockedMinoes(delta) {
|
|
||||||
this.unlockedMinoes.forEach(mino => {
|
|
||||||
mino.update(delta)
|
|
||||||
if (Math.sqrt(mino.position.x * mino.position.x + mino.position.z * mino.position.z) > 25) {
|
|
||||||
this.remove(mino)
|
|
||||||
this.unlockedMinoes.delete(mino)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
update(delta) {
|
|
||||||
this.updateUnlockedMinoes(delta)
|
|
||||||
this.mixer?.update(delta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NextQueue extends THREE.Group {
|
|
||||||
init() {
|
|
||||||
this.pieces = this.positions.map((position) => {
|
|
||||||
let piece = new Tetromino.random()
|
|
||||||
piece.position.copy(position)
|
|
||||||
this.add(piece)
|
|
||||||
return piece
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
shift() {
|
|
||||||
let fistPiece = this.pieces.shift()
|
|
||||||
let lastPiece = new Tetromino.random()
|
|
||||||
this.add(lastPiece)
|
|
||||||
this.pieces.push(lastPiece)
|
|
||||||
this.positions.forEach((position, i) => {
|
|
||||||
this.pieces[i].position.copy(position)
|
|
||||||
})
|
|
||||||
return fistPiece
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
NextQueue.prototype.positions = [P(0, 0), P(0, -3), P(0, -6), P(0, -9), P(0, -12), P(0, -16)]
|
|
||||||
|
|
||||||
const GRAVITY = -20
|
|
||||||
|
|
||||||
class Mino extends THREE.Mesh {
|
|
||||||
constructor() {
|
|
||||||
super(Mino.prototype.geometry)
|
|
||||||
this.velocity = P(50 - 100 * Math.random(), 50 - 100 * Math.random(), 50 - 100 * Math.random())
|
|
||||||
this.rotationAngle = P(Math.random(), Math.random(), Math.random()).normalize()
|
|
||||||
this.angularVelocity = 5 - 10 * Math.random()
|
|
||||||
scene.add(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
update(delta) {
|
|
||||||
this.velocity.y += delta * GRAVITY
|
|
||||||
this.position.addScaledVector(this.velocity, delta)
|
|
||||||
this.rotateOnWorldAxis(this.rotationAngle, delta * this.angularVelocity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const minoFaceShape = new THREE.Shape()
|
|
||||||
minoFaceShape.moveTo(.1, .1)
|
|
||||||
minoFaceShape.lineTo(.1, .9)
|
|
||||||
minoFaceShape.lineTo(.9, .9)
|
|
||||||
minoFaceShape.lineTo(.9, .1)
|
|
||||||
minoFaceShape.lineTo(.1, .1)
|
|
||||||
const minoExtrudeSettings = {
|
|
||||||
steps: 1,
|
|
||||||
depth: .8,
|
|
||||||
bevelEnabled: true,
|
|
||||||
bevelThickness: .1,
|
|
||||||
bevelSize: .1,
|
|
||||||
bevelOffset: 0,
|
|
||||||
bevelSegments: 1
|
|
||||||
}
|
|
||||||
Mino.prototype.geometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings)
|
|
||||||
|
|
||||||
|
|
||||||
const envRenderTarget = new THREE.WebGLCubeRenderTarget(256)
|
|
||||||
envRenderTarget.texture.type = THREE.HalfFloatType
|
|
||||||
const envCamera = new THREE.CubeCamera(1, 1000, envRenderTarget)
|
|
||||||
envCamera.position.set(5, 10)
|
|
||||||
|
|
||||||
class MinoMaterial extends THREE.MeshBasicMaterial {
|
|
||||||
constructor(color) {
|
|
||||||
super({
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
color: color,
|
|
||||||
envMap: envRenderTarget.texture,
|
|
||||||
reflectivity: 0.9,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class GhostMaterial extends THREE.MeshBasicMaterial {
|
|
||||||
constructor(color) {
|
|
||||||
super({
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
color: color,
|
|
||||||
envMap: envRenderTarget.texture,
|
|
||||||
reflectivity: 0.9,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.2
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractTetromino extends THREE.Group {
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
this.add(new Mino())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set facing(facing) {
|
|
||||||
this._facing = facing
|
|
||||||
this.minoesPosition[this.facing].forEach(
|
|
||||||
(position, i) => this.children[i].position.copy(position)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
get facing() {
|
|
||||||
return this._facing
|
|
||||||
}
|
|
||||||
|
|
||||||
canMove(translation, facing = this.facing) {
|
|
||||||
let testPosition = this.position.clone().add(translation)
|
|
||||||
return this.minoesPosition[facing].every(minoPosition => matrix.cellIsEmpty(minoPosition.clone().add(testPosition)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Ghost extends AbstractTetromino {}
|
|
||||||
Ghost.prototype.minoesPosition = [
|
|
||||||
[P(0, 0, 0), P(0, 0, 0), P(0, 0, 0), P(0, 0, 0)],
|
|
||||||
]
|
|
||||||
|
|
||||||
class Tetromino extends AbstractTetromino {
|
|
||||||
static randomBag = []
|
|
||||||
static get random() {
|
|
||||||
if (!this.randomBag.length) this.randomBag = [I, J, L, O, S, T, Z]
|
|
||||||
return this.randomBag.pick()
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
this.rotatedLast = false
|
|
||||||
this.rotationPoint4Used = false
|
|
||||||
this.holdEnabled = true
|
|
||||||
this.facing = 0
|
|
||||||
this.locking = false
|
|
||||||
}
|
|
||||||
|
|
||||||
move(translation, rotatedFacing, rotationPoint) {
|
|
||||||
if (this.canMove(translation, rotatedFacing)) {
|
|
||||||
this.position.add(translation)
|
|
||||||
this.rotatedLast = rotatedFacing
|
|
||||||
if (rotatedFacing != undefined) {
|
|
||||||
this.facing = rotatedFacing
|
|
||||||
if (rotationPoint == 4) this.rotationPoint4Used = true
|
|
||||||
}
|
|
||||||
if (this.canMove(TRANSLATION.DOWN)) {
|
|
||||||
this.locking = false
|
|
||||||
scheduler.clearTimeout(game.lockDown)
|
|
||||||
} else {
|
|
||||||
scheduler.resetTimeout(game.lockDown, this.lockDelay)
|
|
||||||
this.locking = true
|
|
||||||
}
|
|
||||||
this.updateGhost()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rotate(rotation) {
|
|
||||||
let testFacing = (this.facing + rotation) % 4
|
|
||||||
return this.srs[this.facing][rotation].some(
|
|
||||||
(translation, rotationPoint) => this.move(translation, testFacing, rotationPoint)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
set locking(locking) {
|
|
||||||
if (locking) {
|
|
||||||
this.children.forEach(mino => mino.material = this.lockedMaterial)
|
|
||||||
scene.remove(this.ghost)
|
|
||||||
} else {
|
|
||||||
this.children.forEach(mino => mino.material = this.material)
|
|
||||||
scene.add(this.ghost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGhost() {
|
|
||||||
this.ghost.position.copy(this.position)
|
|
||||||
this.ghost.minoesPosition = this.minoesPosition
|
|
||||||
this.ghost.facing = this.facing
|
|
||||||
while (this.ghost.canMove(TRANSLATION.DOWN)) this.ghost.position.y--
|
|
||||||
}
|
|
||||||
|
|
||||||
get tSpin() {
|
|
||||||
return T_SPIN.NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Super Rotation System
|
|
||||||
// freedom of movement = srs[matrix.piece.facing][rotation]
|
|
||||||
Tetromino.prototype.srs = [
|
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(-1, 1), P(0, -2), P(-1, -2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(1, 1), P(0, -2), P(1, -2)] },
|
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(1, -1), P(0, 2), P(1, 2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(1, -1), P(0, 2), P(1, 2)] },
|
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(1, 1), P(0, -2), P(1, -2)], [ROTATION.CCW]: [P(0, 0), P(-1, 0), P(-1, 1), P(0, -2), P(-1, -2)] },
|
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(-1, -1), P(0, 2), P(-1, 2)], [ROTATION.CCW]: [P(0, 0), P(-1, 0), P(-1, -1), P(0, 2), P(-1, 2)] },
|
|
||||||
]
|
|
||||||
Tetromino.prototype.lockedMaterial = new MinoMaterial(0xffffff)
|
|
||||||
Tetromino.prototype.lockDelay = 500
|
|
||||||
|
|
||||||
class I extends Tetromino { }
|
|
||||||
I.prototype.minoesPosition = [
|
|
||||||
[P(-1, 0), P(0, 0), P(1, 0), P(2, 0)],
|
|
||||||
[P(1, 1), P(1, 0), P(1, -1), P(1, -2)],
|
|
||||||
[P(-1, -1), P(0, -1), P(1, -1), P(2, -1)],
|
|
||||||
[P(0, 1), P(0, 0), P(0, -1), P(0, -2)],
|
|
||||||
]
|
|
||||||
I.prototype.srs = [
|
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(-2, 0), P(1, 0), P(-2, -1), P(1, 2)], [ROTATION.CCW]: [P(0, 0), P(-1, 0), P(2, 0), P(-1, 2), P(2, -1)] },
|
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(2, 0), P(-1, 2), P(2, -1)], [ROTATION.CCW]: [P(0, 0), P(2, 0), P(-1, 0), P(2, 1), P(-1, -2)] },
|
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(2, 0), P(-1, 0), P(2, 1), P(-1, -2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(-2, 0), P(1, -2), P(-2, 1)] },
|
|
||||||
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(-2, 0), P(1, -2), P(-2, 1)], [ROTATION.CCW]: [P(0, 0), P(-2, 0), P(1, 0), P(-2, -1), P(1, 2)] },
|
|
||||||
]
|
|
||||||
I.prototype.material = new MinoMaterial(COLORS.I)
|
|
||||||
I.prototype.ghostMaterial = new GhostMaterial(COLORS.I)
|
|
||||||
|
|
||||||
class J extends Tetromino { }
|
|
||||||
J.prototype.minoesPosition = [
|
|
||||||
[P(-1, 1), P(-1, 0), P(0, 0), P(1, 0)],
|
|
||||||
[P(0, 1), P(1, 1), P(0, 0), P(0, -1)],
|
|
||||||
[P(1, -1), P(-1, 0), P(0, 0), P(1, 0)],
|
|
||||||
[P(0, 1), P(-1, -1), P(0, 0), P(0, -1)],
|
|
||||||
]
|
|
||||||
J.prototype.material = new MinoMaterial(COLORS.J)
|
|
||||||
J.prototype.ghostMaterial = new GhostMaterial(COLORS.J)
|
|
||||||
|
|
||||||
class L extends Tetromino { }
|
|
||||||
L.prototype.minoesPosition = [
|
|
||||||
[P(-1, 0), P(0, 0), P(1, 0), P(1, 1)],
|
|
||||||
[P(0, 1), P(0, 0), P(0, -1), P(1, -1)],
|
|
||||||
[P(-1, 0), P(0, 0), P(1, 0), P(-1, -1)],
|
|
||||||
[P(0, 1), P(0, 0), P(0, -1), P(-1, 1)],
|
|
||||||
]
|
|
||||||
L.prototype.material = new MinoMaterial(COLORS.L)
|
|
||||||
L.prototype.ghostMaterial = new GhostMaterial(COLORS.L)
|
|
||||||
|
|
||||||
class O extends Tetromino { }
|
|
||||||
O.prototype.minoesPosition = [
|
|
||||||
[P(0, 0), P(1, 0), P(0, 1), P(1, 1)]
|
|
||||||
]
|
|
||||||
O.prototype.srs = [
|
|
||||||
{ [ROTATION.CW]: [], [ROTATION.CCW]: [] }
|
|
||||||
]
|
|
||||||
O.prototype.material = new MinoMaterial(COLORS.O)
|
|
||||||
O.prototype.ghostMaterial = new GhostMaterial(COLORS.O)
|
|
||||||
|
|
||||||
class S extends Tetromino { }
|
|
||||||
S.prototype.minoesPosition = [
|
|
||||||
[P(-1, 0), P(0, 0), P(0, 1), P(1, 1)],
|
|
||||||
[P(0, 1), P(0, 0), P(1, 0), P(1, -1)],
|
|
||||||
[P(-1, -1), P(0, 0), P(1, 0), P(0, -1)],
|
|
||||||
[P(-1, 1), P(0, 0), P(-1, 0), P(0, -1)],
|
|
||||||
]
|
|
||||||
S.prototype.material = new MinoMaterial(COLORS.S)
|
|
||||||
S.prototype.ghostMaterial = new GhostMaterial(COLORS.S)
|
|
||||||
|
|
||||||
class T extends Tetromino {
|
|
||||||
get tSpin() {
|
|
||||||
if (this.rotatedLast) {
|
|
||||||
let [a, b, c, d] = this.tSlots[matrix.piece.facing]
|
|
||||||
.map(p => !matrix.cellIsEmpty(p.clone().add(this.position)))
|
|
||||||
if (a && b && (c || d))
|
|
||||||
return T_SPIN.T_SPIN
|
|
||||||
else if (c && d && (a || b))
|
|
||||||
return this.rotationPoint4Used ? T_SPIN.T_SPIN : T_SPIN.MINI
|
|
||||||
}
|
|
||||||
return T_SPIN.NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
T.prototype.minoesPosition = [
|
|
||||||
[P(-1, 0), P(0, 0), P(1, 0), P(0, 1)],
|
|
||||||
[P(0, 1), P(0, 0), P(1, 0), P(0, -1)],
|
|
||||||
[P(-1, 0), P(0, 0), P(1, 0), P(0, -1)],
|
|
||||||
[P(0, 1), P(0, 0), P(0, -1), P(-1, 0)],
|
|
||||||
]
|
|
||||||
T.prototype.tSlots = [
|
|
||||||
[P(-1, 1), P(1, 1), P(1, -1), P(-1, -1)],
|
|
||||||
[P(1, 1), P(1, -1), P(-1, -1), P(-1, 1)],
|
|
||||||
[P(1, -1), P(-1, -1), P(-1, 1), P(1, 1)],
|
|
||||||
[P(-1, -1), P(-1, 1), P(1, 1), P(1, -1)],
|
|
||||||
]
|
|
||||||
T.prototype.material = new MinoMaterial(COLORS.T)
|
|
||||||
T.prototype.ghostMaterial = new GhostMaterial(COLORS.T)
|
|
||||||
|
|
||||||
class Z extends Tetromino { }
|
|
||||||
Z.prototype.minoesPosition = [
|
|
||||||
[P(-1, 1), P(0, 1), P(0, 0), P(1, 0)],
|
|
||||||
[P(1, 1), P(1, 0), P(0, 0), P(0, -1)],
|
|
||||||
[P(-1, 0), P(0, 0), P(0, -1), P(1, -1)],
|
|
||||||
[P(0, 1), P(-1, 0), P(0, 0), P(-1, -1)]
|
|
||||||
]
|
|
||||||
Z.prototype.material = new MinoMaterial(COLORS.Z)
|
|
||||||
Z.prototype.ghostMaterial = new GhostMaterial(COLORS.Z)
|
|
||||||
|
|
||||||
|
|
||||||
/* world */
|
|
||||||
|
|
||||||
const loadingManager = new THREE.LoadingManager()
|
const loadingManager = new THREE.LoadingManager()
|
||||||
loadingManager.onStart = function (url, itemsLoaded, itemsTotal) {
|
loadingManager.onStart = function (url, itemsLoaded, itemsTotal) {
|
||||||
@ -469,10 +37,11 @@ loadingManager.onError = function (url) {
|
|||||||
loadingPercent.innerText = "Erreur"
|
loadingPercent.innerText = "Erreur"
|
||||||
}
|
}
|
||||||
|
|
||||||
const world = {}
|
|
||||||
|
|
||||||
const scene = new THREE.Scene()
|
const scene = new THREE.Scene()
|
||||||
|
|
||||||
|
scene.vortex = new Vortex(loadingManager)
|
||||||
|
scene.add(scene.vortex)
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({
|
const renderer = new THREE.WebGLRenderer({
|
||||||
powerPreference: "high-performance",
|
powerPreference: "high-performance",
|
||||||
antialias: true,
|
antialias: true,
|
||||||
@ -483,99 +52,22 @@ renderer.setClearColor(0x000000, 10)
|
|||||||
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
||||||
document.body.appendChild(renderer.domElement)
|
document.body.appendChild(renderer.domElement)
|
||||||
|
|
||||||
world.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
|
scene.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
|
||||||
world.camera.position.set(5, 0, 16)
|
scene.camera.position.set(5, 0, 16)
|
||||||
|
|
||||||
|
scene.ambientLight = new THREE.AmbientLight(0xffffff, 0.2)
|
||||||
|
scene.add(scene.ambientLight)
|
||||||
|
|
||||||
const controls = new TetraControls(world.camera, renderer.domElement)
|
scene.directionalLight = new THREE.DirectionalLight(0xffffff, 20)
|
||||||
|
scene.directionalLight.position.set(5, -100, -16)
|
||||||
|
scene.add(scene.directionalLight)
|
||||||
|
|
||||||
|
const holdQueue = new HoldQueue()
|
||||||
const GLOBAL_ROTATION = 0.028
|
|
||||||
|
|
||||||
const darkTextureRotation = 0.006
|
|
||||||
const darkMoveForward = 0.007
|
|
||||||
|
|
||||||
const colorFullTextureRotation = 0.006
|
|
||||||
const colorFullMoveForward = 0.02
|
|
||||||
|
|
||||||
const commonCylinderGeometry = new THREE.CylinderGeometry(25, 25, 500, 12, 1, true)
|
|
||||||
|
|
||||||
world.darkCylinder = new THREE.Mesh(
|
|
||||||
commonCylinderGeometry,
|
|
||||||
new THREE.MeshLambertMaterial({
|
|
||||||
side: THREE.BackSide,
|
|
||||||
map: new THREE.TextureLoader(loadingManager).load("images/plasma.jpg", (texture) => {
|
|
||||||
texture.wrapS = THREE.RepeatWrapping
|
|
||||||
texture.wrapT = THREE.MirroredRepeatWrapping
|
|
||||||
texture.repeat.set(1, 1)
|
|
||||||
}),
|
|
||||||
blending: THREE.AdditiveBlending,
|
|
||||||
opacity: 0.1
|
|
||||||
})
|
|
||||||
)
|
|
||||||
world.darkCylinder.position.set(5, 10, -10)
|
|
||||||
scene.add(world.darkCylinder)
|
|
||||||
|
|
||||||
world.colorFullCylinder = new THREE.Mesh(
|
|
||||||
commonCylinderGeometry,
|
|
||||||
new THREE.MeshBasicMaterial({
|
|
||||||
side: THREE.BackSide,
|
|
||||||
map: new THREE.TextureLoader(loadingManager).load("images/plasma2.jpg", (texture) => {
|
|
||||||
texture.wrapS = THREE.RepeatWrapping
|
|
||||||
texture.wrapT = THREE.MirroredRepeatWrapping
|
|
||||||
texture.repeat.set(2, 1)
|
|
||||||
}),
|
|
||||||
blending: THREE.AdditiveBlending,
|
|
||||||
opacity: 0.6
|
|
||||||
})
|
|
||||||
)
|
|
||||||
world.colorFullCylinder.position.set(5, 10, -10)
|
|
||||||
scene.add(world.colorFullCylinder)
|
|
||||||
|
|
||||||
world.ambientLight = new THREE.AmbientLight(0xffffff, 0.2)
|
|
||||||
scene.add(world.ambientLight)
|
|
||||||
|
|
||||||
world.directionalLight = new THREE.DirectionalLight(0xffffff, 20)
|
|
||||||
world.directionalLight.position.set(5, -100, -16)
|
|
||||||
scene.add(world.directionalLight)
|
|
||||||
|
|
||||||
const holdQueue = new THREE.Group()
|
|
||||||
holdQueue.position.set(-4, SKYLINE - 2)
|
|
||||||
scene.add(holdQueue)
|
scene.add(holdQueue)
|
||||||
const matrix = new Matrix()
|
const matrix = new Matrix()
|
||||||
scene.add(matrix)
|
scene.add(matrix)
|
||||||
scene.add(matrix.edge)
|
|
||||||
const nextQueue = new NextQueue()
|
const nextQueue = new NextQueue()
|
||||||
nextQueue.position.set(13, SKYLINE - 2)
|
|
||||||
scene.add(nextQueue)
|
scene.add(nextQueue)
|
||||||
Tetromino.prototype.ghost = new Ghost()
|
|
||||||
|
|
||||||
let clock = new THREE.Clock()
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
|
|
||||||
const delta = clock.getDelta()
|
|
||||||
|
|
||||||
world.darkCylinder.rotation.y += GLOBAL_ROTATION * delta
|
|
||||||
world.darkCylinder.material.map.offset.y += darkMoveForward * delta
|
|
||||||
world.darkCylinder.material.map.offset.x += darkTextureRotation * delta
|
|
||||||
|
|
||||||
world.colorFullCylinder.rotation.y += GLOBAL_ROTATION * delta
|
|
||||||
world.colorFullCylinder.material.map.offset.y += colorFullMoveForward * delta
|
|
||||||
world.colorFullCylinder.material.map.offset.x += colorFullTextureRotation * delta
|
|
||||||
|
|
||||||
controls.update()
|
|
||||||
matrix.update(delta)
|
|
||||||
gui.update()
|
|
||||||
|
|
||||||
renderer.render(scene, world.camera)
|
|
||||||
envCamera.update(renderer, scene)
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("resize", () => {
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
||||||
world.camera.aspect = window.innerWidth / window.innerHeight
|
|
||||||
world.camera.updateProjectionMatrix()
|
|
||||||
})
|
|
||||||
|
|
||||||
messagesSpan.onanimationend = function (event) {
|
messagesSpan.onanimationend = function (event) {
|
||||||
event.target.remove()
|
event.target.remove()
|
||||||
@ -596,19 +88,17 @@ let game = {
|
|||||||
holdQueue.remove(holdQueue.piece)
|
holdQueue.remove(holdQueue.piece)
|
||||||
holdQueue.piece = undefined
|
holdQueue.piece = undefined
|
||||||
if (nextQueue.pieces) nextQueue.pieces.forEach(piece => nextQueue.remove(piece))
|
if (nextQueue.pieces) nextQueue.pieces.forEach(piece => nextQueue.remove(piece))
|
||||||
while(matrix.children.length) matrix.remove(matrix.children[0])
|
|
||||||
matrix.init()
|
matrix.init()
|
||||||
|
|
||||||
scene.remove(matrix.piece)
|
scene.remove(matrix.piece)
|
||||||
matrix.piece = null
|
matrix.piece = null
|
||||||
world.music.currentTime = 0
|
scene.music.currentTime = 0
|
||||||
matrix.edge.visible = true
|
matrix.visible = true
|
||||||
|
|
||||||
this.playing = true
|
this.playing = true
|
||||||
stats.clock.start()
|
stats.clock.start()
|
||||||
|
|
||||||
renderer.domElement.tabIndex = 1
|
renderer.domElement.tabIndex = 1
|
||||||
renderer.domElement.onblur = this.pause
|
|
||||||
gui.domElement.tabIndex = 1
|
gui.domElement.tabIndex = 1
|
||||||
gui.domElement.onfocus = game.pause
|
gui.domElement.onfocus = game.pause
|
||||||
|
|
||||||
@ -621,6 +111,7 @@ let game = {
|
|||||||
resume: function() {
|
resume: function() {
|
||||||
document.onkeydown = onkeydown
|
document.onkeydown = onkeydown
|
||||||
document.onkeyup = onkeyup
|
document.onkeyup = onkeyup
|
||||||
|
window.onblur = game.pause
|
||||||
|
|
||||||
document.body.classList.remove("pause")
|
document.body.classList.remove("pause")
|
||||||
gui.resumeButton.hide()
|
gui.resumeButton.hide()
|
||||||
@ -628,22 +119,16 @@ let game = {
|
|||||||
|
|
||||||
stats.clock.start()
|
stats.clock.start()
|
||||||
stats.clock.elapsedTime = stats.elapsedTime
|
stats.clock.elapsedTime = stats.elapsedTime
|
||||||
world.music.play()
|
scene.music.play()
|
||||||
|
|
||||||
if (matrix.piece) scheduler.setInterval(game.fall, stats.fallPeriod)
|
if (matrix.piece) scheduler.setInterval(game.fall, stats.fallPeriod)
|
||||||
else this.generate()
|
else this.generate()
|
||||||
},
|
},
|
||||||
|
|
||||||
generate: function(nextPiece=nextQueue.shift()) {
|
generate: function(nextPiece=nextQueue.shift()) {
|
||||||
|
nextPiece.lockDelay = stats.lockDelay
|
||||||
matrix.piece = nextPiece
|
matrix.piece = nextPiece
|
||||||
matrix.piece.position.set(4, SKYLINE)
|
matrix.piece.onlockdown = game.lockDown
|
||||||
matrix.piece.lockDelay = stats.lockDelay
|
|
||||||
scene.add(matrix.piece)
|
|
||||||
matrix.piece.updateGhost()
|
|
||||||
matrix.piece.ghost.children.forEach((mino) => {
|
|
||||||
mino.material = matrix.piece.ghostMaterial
|
|
||||||
})
|
|
||||||
scene.add(matrix.piece.ghost)
|
|
||||||
|
|
||||||
if (matrix.piece.canMove(TRANSLATION.NONE)) {
|
if (matrix.piece.canMove(TRANSLATION.NONE)) {
|
||||||
scheduler.setInterval(game.fall, stats.fallPeriod)
|
scheduler.setInterval(game.fall, stats.fallPeriod)
|
||||||
@ -661,16 +146,16 @@ let game = {
|
|||||||
scheduler.clearInterval(game.fall)
|
scheduler.clearInterval(game.fall)
|
||||||
|
|
||||||
if (matrix.lock(matrix.piece)) {
|
if (matrix.lock(matrix.piece)) {
|
||||||
scene.remove(matrix.piece)
|
|
||||||
let tSpin = matrix.piece.tSpin
|
let tSpin = matrix.piece.tSpin
|
||||||
let nbClearedLines = matrix.clearLines()
|
let nbClearedLines = matrix.clearLines()
|
||||||
|
matrix.remove(matrix.piece)
|
||||||
if (settings.sfxVolume) {
|
if (settings.sfxVolume) {
|
||||||
if (nbClearedLines == 4 || (tSpin && nbClearedLines)) {
|
if (nbClearedLines == 4 || (tSpin && nbClearedLines)) {
|
||||||
world.tetrisSound.currentTime = 0
|
scene.tetrisSound.currentTime = 0
|
||||||
world.tetrisSound.play()
|
scene.tetrisSound.play()
|
||||||
} else if (nbClearedLines || tSpin) {
|
} else if (nbClearedLines || tSpin) {
|
||||||
world.lineClearSound.currentTime = 0
|
scene.lineClearSound.currentTime = 0
|
||||||
world.lineClearSound.play()
|
scene.lineClearSound.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stats.lockDown(nbClearedLines, tSpin)
|
stats.lockDown(nbClearedLines, tSpin)
|
||||||
@ -690,8 +175,9 @@ let game = {
|
|||||||
scheduler.clearTimeout(repeat)
|
scheduler.clearTimeout(repeat)
|
||||||
scheduler.clearInterval(autorepeat)
|
scheduler.clearInterval(autorepeat)
|
||||||
|
|
||||||
world.music.pause()
|
scene.music.pause()
|
||||||
document.onkeydown = null
|
document.onkeydown = null
|
||||||
|
window.onblur = null
|
||||||
|
|
||||||
pauseSpan.onfocus = game.resume
|
pauseSpan.onfocus = game.resume
|
||||||
document.body.classList.add("pause")
|
document.body.classList.add("pause")
|
||||||
@ -703,13 +189,12 @@ let game = {
|
|||||||
matrix.piece.locking = false
|
matrix.piece.locking = false
|
||||||
|
|
||||||
document.onkeydown = null
|
document.onkeydown = null
|
||||||
renderer.domElement.onblur = null
|
window.onblur = null
|
||||||
renderer.domElement.onfocus = null
|
renderer.domElement.onfocus = null
|
||||||
gui.domElement.onfocus = null
|
gui.domElement.onfocus = null
|
||||||
game.playing = false
|
game.playing = false
|
||||||
world.music.pause()
|
scene.music.pause()
|
||||||
stats.clock.stop()
|
stats.clock.stop()
|
||||||
localStorage["teTraHighScore"] = stats.highScore
|
|
||||||
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>GAME<br/>OVER</h1>` })
|
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>GAME<br/>OVER</h1>` })
|
||||||
|
|
||||||
gui.pauseButton.hide()
|
gui.pauseButton.hide()
|
||||||
@ -718,7 +203,10 @@ let game = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("pieceLocked", game.lockDown)
|
|
||||||
|
/* Handle player inputs */
|
||||||
|
|
||||||
|
const controls = new TetraControls(scene.camera, renderer.domElement)
|
||||||
|
|
||||||
let playerActions = {
|
let playerActions = {
|
||||||
moveLeft: () => matrix.piece.move(TRANSLATION.LEFT),
|
moveLeft: () => matrix.piece.move(TRANSLATION.LEFT),
|
||||||
@ -735,15 +223,15 @@ let playerActions = {
|
|||||||
|
|
||||||
hardDrop: function () {
|
hardDrop: function () {
|
||||||
scheduler.clearTimeout(game.lockDown)
|
scheduler.clearTimeout(game.lockDown)
|
||||||
world.hardDropSound.play()
|
scene.hardDropSound.play()
|
||||||
if (settings.sfxVolume) {
|
if (settings.sfxVolume) {
|
||||||
world.hardDropSound.currentTime = 0
|
scene.hardDropSound.currentTime = 0
|
||||||
world.hardDropSound.play()
|
scene.hardDropSound.play()
|
||||||
}
|
}
|
||||||
while (matrix.piece.move(TRANSLATION.DOWN)) stats.score += 2
|
while (matrix.piece.move(TRANSLATION.DOWN)) stats.score += 2
|
||||||
game.lockDown()
|
game.lockDown()
|
||||||
hardDroppedMatrix.reset()
|
matrix.hardDropAnimation.reset()
|
||||||
hardDroppedMatrix.play()
|
matrix.hardDropAnimation.play()
|
||||||
},
|
},
|
||||||
|
|
||||||
hold: function () {
|
hold: function () {
|
||||||
@ -753,11 +241,6 @@ let playerActions = {
|
|||||||
|
|
||||||
let heldpiece = holdQueue.piece
|
let heldpiece = holdQueue.piece
|
||||||
holdQueue.piece = matrix.piece
|
holdQueue.piece = matrix.piece
|
||||||
holdQueue.piece.holdEnabled = false
|
|
||||||
holdQueue.piece.locking = false
|
|
||||||
holdQueue.piece.position.set(0, 0)
|
|
||||||
holdQueue.piece.facing = FACING.NORTH
|
|
||||||
holdQueue.add(holdQueue.piece)
|
|
||||||
game.generate(heldpiece)
|
game.generate(heldpiece)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -765,7 +248,6 @@ let playerActions = {
|
|||||||
pause: game.pause,
|
pause: game.pause,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle player inputs
|
|
||||||
const REPEATABLE_ACTIONS = [
|
const REPEATABLE_ACTIONS = [
|
||||||
playerActions.moveLeft,
|
playerActions.moveLeft,
|
||||||
playerActions.moveRight,
|
playerActions.moveRight,
|
||||||
@ -828,46 +310,65 @@ function onkeyup(event) {
|
|||||||
/* Sounds */
|
/* Sounds */
|
||||||
|
|
||||||
const listener = new THREE.AudioListener()
|
const listener = new THREE.AudioListener()
|
||||||
world.camera.add( listener )
|
scene.camera.add( listener )
|
||||||
const audioLoader = new THREE.AudioLoader(loadingManager)
|
const audioLoader = new THREE.AudioLoader(loadingManager)
|
||||||
world.music = new THREE.Audio(listener)
|
|
||||||
|
scene.music = new THREE.Audio(listener)
|
||||||
audioLoader.load('audio/Tetris_CheDDer_OC_ReMix.mp3', function( buffer ) {
|
audioLoader.load('audio/Tetris_CheDDer_OC_ReMix.mp3', function( buffer ) {
|
||||||
world.music.setBuffer(buffer)
|
scene.music.setBuffer(buffer)
|
||||||
world.music.setLoop(true)
|
scene.music.setLoop(true)
|
||||||
world.music.setVolume(settings.musicVolume/100)
|
scene.music.setVolume(settings.musicVolume/100)
|
||||||
if (game.playing) world.music.play()
|
if (game.playing) scene.music.play()
|
||||||
})
|
})
|
||||||
world.lineClearSound = new THREE.Audio(listener)
|
scene.lineClearSound = new THREE.Audio(listener)
|
||||||
audioLoader.load('audio/line-clear.ogg', function( buffer ) {
|
audioLoader.load('audio/line-clear.ogg', function( buffer ) {
|
||||||
world.lineClearSound.setBuffer(buffer)
|
scene.lineClearSound.setBuffer(buffer)
|
||||||
world.lineClearSound.setVolume(settings.sfxVolume/100)
|
scene.lineClearSound.setVolume(settings.sfxVolume/100)
|
||||||
})
|
})
|
||||||
world.tetrisSound = new THREE.Audio(listener)
|
scene.tetrisSound = new THREE.Audio(listener)
|
||||||
audioLoader.load('audio/tetris.ogg', function( buffer ) {
|
audioLoader.load('audio/tetris.ogg', function( buffer ) {
|
||||||
world.tetrisSound.setBuffer(buffer)
|
scene.tetrisSound.setBuffer(buffer)
|
||||||
world.tetrisSound.setVolume(settings.sfxVolume/100)
|
scene.tetrisSound.setVolume(settings.sfxVolume/100)
|
||||||
})
|
})
|
||||||
world.hardDropSound = new THREE.Audio(listener)
|
scene.hardDropSound = new THREE.Audio(listener)
|
||||||
audioLoader.load('audio/hard-drop.wav', function( buffer ) {
|
audioLoader.load('audio/hard-drop.wav', function( buffer ) {
|
||||||
world.hardDropSound.setBuffer(buffer)
|
scene.hardDropSound.setBuffer(buffer)
|
||||||
world.hardDropSound.setVolume(settings.sfxVolume/100)
|
scene.hardDropSound.setVolume(settings.sfxVolume/100)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
let scheduler = new Scheduler()
|
let stats = new Stats()
|
||||||
let stats = new Stats()
|
let settings = new Settings()
|
||||||
let settings = new Settings(playerActions)
|
|
||||||
|
|
||||||
var gui = new TetraGUI(game, settings, stats, world)
|
var gui = new TetraGUI(game, settings, stats, scene)
|
||||||
|
|
||||||
gui.load()
|
const clock = new THREE.Clock()
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
|
||||||
|
const delta = clock.getDelta()
|
||||||
|
|
||||||
|
scene.vortex.update(delta)
|
||||||
|
matrix.update(delta)
|
||||||
|
controls.update()
|
||||||
|
gui.update()
|
||||||
|
|
||||||
|
renderer.render(scene, scene.camera)
|
||||||
|
environnement.camera.update(renderer, scene)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||||
|
scene.camera.aspect = window.innerWidth / window.innerHeight
|
||||||
|
scene.camera.updateProjectionMatrix()
|
||||||
|
})
|
||||||
|
|
||||||
window.onbeforeunload = function (event) {
|
window.onbeforeunload = function (event) {
|
||||||
gui.save()
|
gui.save()
|
||||||
if (game.playing) return false
|
localStorage["teTraHighScore"] = stats.highScore
|
||||||
|
return !game.playing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.register('jsm/service-worker.js');
|
navigator.serviceWorker.register('./jsm/service-worker.js');
|
||||||
}
|
}
|
305
jsm/Stats.js
305
jsm/Stats.js
@ -1,165 +1,186 @@
|
|||||||
import { Clock } from 'three'
|
import { Clock } from 'three'
|
||||||
import { DELAY, T_SPIN, AWARDED_LINE_CLEARS, CLEARED_LINES_NAMES } from './common.js'
|
import { T_SPIN } from './gamelogic.js'
|
||||||
|
|
||||||
|
|
||||||
|
// score = AWARDED_LINE_CLEARS[tSpin][nbClearedLines]
|
||||||
|
const AWARDED_LINE_CLEARS = {
|
||||||
|
[T_SPIN.NONE] : [0, 1, 3, 5, 8],
|
||||||
|
[T_SPIN.MINI] : [1, 2],
|
||||||
|
[T_SPIN.T_SPIN]: [4, 8, 12, 16]
|
||||||
|
}
|
||||||
|
|
||||||
|
const CLEARED_LINES_NAMES = [
|
||||||
|
"",
|
||||||
|
"SOLO",
|
||||||
|
"DUO",
|
||||||
|
"TRIO",
|
||||||
|
"TETRA",
|
||||||
|
]
|
||||||
|
|
||||||
|
const DELAY = {
|
||||||
|
LOCK: 500,
|
||||||
|
FALL: 1000,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Stats {
|
class Stats {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.clock = new Clock(false)
|
this.clock = new Clock(false)
|
||||||
this.clock.timeFormat = new Intl.DateTimeFormat("fr-FR", {
|
this.timeFormat = new Intl.DateTimeFormat("fr-FR", {
|
||||||
hour: "numeric",
|
hour: "numeric",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
second: "2-digit",
|
second: "2-digit",
|
||||||
timeZone: "UTC"
|
timeZone: "UTC"
|
||||||
})
|
})
|
||||||
this.elapsedTime = 0
|
this.elapsedTime = 0
|
||||||
|
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._level = 0
|
this._level = 0
|
||||||
this._score = 0
|
this._score = 0
|
||||||
this.goal = 0
|
this.goal = 0
|
||||||
this.highScore = Number(localStorage["teTraHighScore"]) || 0
|
this.highScore = Number(localStorage["teTraHighScore"]) || 0
|
||||||
this.combo = 0
|
this.combo = 0
|
||||||
this.b2b = 0
|
this.b2b = 0
|
||||||
this.startTime = new Date()
|
this.startTime = new Date()
|
||||||
this.lockDelay = DELAY.LOCK
|
this.lockDelay = DELAY.LOCK
|
||||||
this.totalClearedLines = 0
|
this.totalClearedLines = 0
|
||||||
this.nbTetra = 0
|
this.nbTetra = 0
|
||||||
this.nbTSpin = 0
|
this.nbTSpin = 0
|
||||||
this.maxCombo = 0
|
this.maxCombo = 0
|
||||||
this.maxB2B = 0
|
this.maxB2B = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
set score(score) {
|
set score(score) {
|
||||||
this._score = score
|
this._score = score
|
||||||
if (score > this.highScore) {
|
if (score > this.highScore) {
|
||||||
this.highScore = score
|
this.highScore = score
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get score() {
|
get score() {
|
||||||
return this._score
|
return this._score
|
||||||
}
|
}
|
||||||
|
|
||||||
set level(level) {
|
set level(level) {
|
||||||
this._level = level
|
this._level = level
|
||||||
this.goal += level * 5
|
this.goal += level * 5
|
||||||
if (level <= 20) this.fallPeriod = 1000 * Math.pow(0.8 - ((level - 1) * 0.007), level - 1)
|
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)
|
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>` })
|
messagesSpan.addNewChild("div", { className: "show-level-animation", innerHTML: `<h1>NIVEAU<br/>${this.level}</h1>` })
|
||||||
}
|
}
|
||||||
|
|
||||||
get level() {
|
get level() {
|
||||||
return this._level
|
return this._level
|
||||||
}
|
}
|
||||||
|
|
||||||
set combo(combo) {
|
set combo(combo) {
|
||||||
this._combo = combo
|
this._combo = combo
|
||||||
if (combo > this.maxCombo) this.maxCombo = combo
|
if (combo > this.maxCombo) this.maxCombo = combo
|
||||||
}
|
}
|
||||||
|
|
||||||
get combo() {
|
get combo() {
|
||||||
return this._combo
|
return this._combo
|
||||||
}
|
}
|
||||||
|
|
||||||
set b2b(b2b) {
|
set b2b(b2b) {
|
||||||
this._b2b = b2b
|
this._b2b = b2b
|
||||||
if (b2b > this.maxB2B) this.maxB2B = b2b
|
if (b2b > this.maxB2B) this.maxB2B = b2b
|
||||||
}
|
}
|
||||||
|
|
||||||
get b2b() {
|
get b2b() {
|
||||||
return this._b2b
|
return this._b2b
|
||||||
}
|
}
|
||||||
|
|
||||||
get time() {
|
get time() {
|
||||||
return this.clock.timeFormat.format(this.clock.getElapsedTime() * 1000)
|
return this.timeFormat.format(this.clock.getElapsedTime() * 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockDown(nbClearedLines, tSpin) {
|
lockDown(nbClearedLines, tSpin) {
|
||||||
this.totalClearedLines += nbClearedLines
|
this.totalClearedLines += nbClearedLines
|
||||||
if (nbClearedLines == 4) this.nbTetra++
|
if (nbClearedLines == 4) this.nbTetra++
|
||||||
if (tSpin == T_SPIN.T_SPIN) this.nbTSpin++
|
if (tSpin == T_SPIN.T_SPIN) this.nbTSpin++
|
||||||
|
|
||||||
// Cleared lines & T-Spin
|
// Cleared lines & T-Spin
|
||||||
let awardedLineClears = AWARDED_LINE_CLEARS[tSpin][nbClearedLines]
|
let awardedLineClears = AWARDED_LINE_CLEARS[tSpin][nbClearedLines]
|
||||||
let patternScore = 100 * this.level * awardedLineClears
|
let patternScore = 100 * this.level * awardedLineClears
|
||||||
if (tSpin) messagesSpan.addNewChild("div", {
|
if (tSpin) messagesSpan.addNewChild("div", {
|
||||||
className: "rotate-in-animation",
|
className: "rotate-in-animation",
|
||||||
innerHTML: tSpin
|
innerHTML: tSpin
|
||||||
})
|
})
|
||||||
if (nbClearedLines) messagesSpan.addNewChild("div", {
|
if (nbClearedLines) messagesSpan.addNewChild("div", {
|
||||||
className: "zoom-in-animation",
|
className: "zoom-in-animation",
|
||||||
innerHTML: CLEARED_LINES_NAMES[nbClearedLines]
|
innerHTML: CLEARED_LINES_NAMES[nbClearedLines]
|
||||||
})
|
})
|
||||||
if (patternScore) {
|
if (patternScore) {
|
||||||
messagesSpan.addNewChild("div", {
|
messagesSpan.addNewChild("div", {
|
||||||
className: "zoom-in-animation",
|
className: "zoom-in-animation",
|
||||||
style: "animation-delay: .2s",
|
style: "animation-delay: .2s",
|
||||||
innerHTML: patternScore
|
innerHTML: patternScore
|
||||||
})
|
})
|
||||||
this.score += patternScore
|
this.score += patternScore
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combo
|
// Combo
|
||||||
if (nbClearedLines) {
|
if (nbClearedLines) {
|
||||||
this.combo++
|
this.combo++
|
||||||
if (this.combo >= 1) {
|
if (this.combo >= 1) {
|
||||||
let comboScore = (nbClearedLines == 1 ? 20 : 50) * this.combo * this.level
|
let comboScore = (nbClearedLines == 1 ? 20 : 50) * this.combo * this.level
|
||||||
if (this.combo == 1) {
|
if (this.combo == 1) {
|
||||||
messagesSpan.addNewChild("div", {
|
messagesSpan.addNewChild("div", {
|
||||||
className: "zoom-in-animation",
|
className: "zoom-in-animation",
|
||||||
style: "animation-delay: .4s",
|
style: "animation-delay: .4s",
|
||||||
innerHTML: `COMBO<br/>${comboScore}`
|
innerHTML: `COMBO<br/>${comboScore}`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
messagesSpan.addNewChild("div", {
|
messagesSpan.addNewChild("div", {
|
||||||
className: "zoom-in-animation",
|
className: "zoom-in-animation",
|
||||||
style: "animation-delay: .4s",
|
style: "animation-delay: .4s",
|
||||||
innerHTML: `COMBO x${this.combo}<br/>${comboScore}`
|
innerHTML: `COMBO x${this.combo}<br/>${comboScore}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.score += comboScore
|
this.score += comboScore
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.combo = -1
|
this.combo = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Back to back sequence
|
// Back to back sequence
|
||||||
if ((nbClearedLines == 4) || (tSpin && nbClearedLines)) {
|
if ((nbClearedLines == 4) || (tSpin && nbClearedLines)) {
|
||||||
this.b2b++
|
this.b2b++
|
||||||
if (this.b2b >= 1) {
|
if (this.b2b >= 1) {
|
||||||
let b2bScore = patternScore / 2
|
let b2bScore = patternScore / 2
|
||||||
if (this.b2b == 1) {
|
if (this.b2b == 1) {
|
||||||
messagesSpan.addNewChild("div", {
|
messagesSpan.addNewChild("div", {
|
||||||
className: "zoom-in-animation",
|
className: "zoom-in-animation",
|
||||||
style: "animation-delay: .4s",
|
style: "animation-delay: .4s",
|
||||||
innerHTML: `BOUT À BOUT<br/>${b2bScore}`
|
innerHTML: `BOUT À BOUT<br/>${b2bScore}`
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
messagesSpan.addNewChild("div", {
|
messagesSpan.addNewChild("div", {
|
||||||
className: "zoom-in-animation",
|
className: "zoom-in-animation",
|
||||||
style: "animation-delay: .4s",
|
style: "animation-delay: .4s",
|
||||||
innerHTML: `BOUT À BOUT x${this.b2b}<br/>${b2bScore}`
|
innerHTML: `BOUT À BOUT x${this.b2b}<br/>${b2bScore}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.score += b2bScore
|
this.score += b2bScore
|
||||||
}
|
}
|
||||||
} else if (nbClearedLines && !tSpin) {
|
} else if (nbClearedLines && !tSpin) {
|
||||||
if (this.b2b >= 1) {
|
if (this.b2b >= 1) {
|
||||||
messagesSpan.addNewChild("div", {
|
messagesSpan.addNewChild("div", {
|
||||||
className: "zoom-in-animation",
|
className: "zoom-in-animation",
|
||||||
style: "animation-delay: .4s",
|
style: "animation-delay: .4s",
|
||||||
innerHTML: `FIN DU BOUT À BOUT`
|
innerHTML: `FIN DU BOUT À BOUT`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.b2b = -1
|
this.b2b = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
this.goal -= awardedLineClears
|
this.goal -= awardedLineClears
|
||||||
if (this.goal <= 0) this.level++
|
if (this.goal <= 0) this.level++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
|
||||||
import * as FPS from 'three/addons/libs/stats.module.js';
|
import * as FPS from 'three/addons/libs/stats.module.js'
|
||||||
|
import { I, J, L, O, S, T, Z } from './gamelogic.js'
|
||||||
|
|
||||||
|
|
||||||
class TetraGUI extends GUI {
|
class TetraGUI extends GUI {
|
||||||
constructor(game, settings, stats, world) {
|
constructor(game, settings, stats, scene) {
|
||||||
super({title: "teTra"})
|
super({title: "teTra"})
|
||||||
|
|
||||||
this.startButton = this.add(game, "start").name("Jouer").hide()
|
this.startButton = this.add(game, "start").name("Jouer").hide()
|
||||||
@ -51,44 +52,46 @@ class TetraGUI extends GUI {
|
|||||||
this.settings.volume = this.settings.addFolder("Volume").open()
|
this.settings.volume = this.settings.addFolder("Volume").open()
|
||||||
this.settings.volume.add(settings,"musicVolume").name("Musique").min(0).max(100).step(1).onChange((volume) => {
|
this.settings.volume.add(settings,"musicVolume").name("Musique").min(0).max(100).step(1).onChange((volume) => {
|
||||||
if (volume) {
|
if (volume) {
|
||||||
world.music.setVolume(volume/100)
|
scene.music.setVolume(volume/100)
|
||||||
if (game.playing) world.music.play()
|
if (game.playing) scene.music.play()
|
||||||
} else {
|
} else {
|
||||||
world.music.pause()
|
scene.music.pause()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.settings.volume.add(settings,"sfxVolume").name("Effets").min(0).max(100).step(1).onChange((volume) => {
|
this.settings.volume.add(settings,"sfxVolume").name("Effets").min(0).max(100).step(1).onChange((volume) => {
|
||||||
world.lineClearSound.setVolume(volume/100)
|
scene.lineClearSound.setVolume(volume/100)
|
||||||
world.tetrisSound.setVolume(volume/100)
|
scene.tetrisSound.setVolume(volume/100)
|
||||||
world.hardDropSound.setVolume(volume/100)
|
scene.hardDropSound.setVolume(volume/100)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (window.location.search.includes("debug")) {
|
if (window.location.search.includes("debug")) {
|
||||||
this.debug = this.addFolder("debug")
|
this.debug = this.addFolder("debug")
|
||||||
let cameraPositionFolder = this.debug.addFolder("camera.position")
|
let cameraPositionFolder = this.debug.addFolder("camera.position")
|
||||||
cameraPositionFolder.add(world.camera.position, "x")
|
cameraPositionFolder.add(scene.camera.position, "x")
|
||||||
cameraPositionFolder.add(world.camera.position, "y")
|
cameraPositionFolder.add(scene.camera.position, "y")
|
||||||
cameraPositionFolder.add(world.camera.position, "z")
|
cameraPositionFolder.add(scene.camera.position, "z")
|
||||||
|
|
||||||
let lightFolder = this.debug.addFolder("lights intensity")
|
let lightFolder = this.debug.addFolder("lights intensity")
|
||||||
lightFolder.add(world.ambientLight, "intensity").name("ambient").min(0).max(20)
|
lightFolder.add(scene.ambientLight, "intensity").name("ambient").min(0).max(20)
|
||||||
lightFolder.add(world.directionalLight, "intensity").name("directional").min(0).max(20)
|
lightFolder.add(scene.directionalLight, "intensity").name("directional").min(0).max(20)
|
||||||
|
|
||||||
let materialsFolder = this.debug.addFolder("materials opacity")
|
let materialsFolder = this.debug.addFolder("materials opacity")
|
||||||
materialsFolder.add(world.darkCylinder.material, "opacity").name("dark").min(0).max(1)
|
materialsFolder.add(scene.vortex.darkCylinder.material, "opacity").name("dark").min(0).max(1)
|
||||||
materialsFolder.add(world.colorFullCylinder.material, "opacity").name("colorFull").min(0).max(1)
|
materialsFolder.add(scene.vortex.colorFullCylinder.material, "opacity").name("colorFull").min(0).max(1)
|
||||||
/*materialsFolder.add(I.prototype.material, "reflectivity").min(0).max(2).onChange(() => {
|
materialsFolder.add(I.prototype.material, "reflectivity").min(0).max(2).onChange(() => {
|
||||||
J.prototype.material.reflectivity = I.prototype.material.reflectivity
|
J.prototype.material.reflectivity = I.prototype.material.reflectivity
|
||||||
L.prototype.material.reflectivity = I.prototype.material.reflectivity
|
L.prototype.material.reflectivity = I.prototype.material.reflectivity
|
||||||
O.prototype.material.reflectivity = I.prototype.material.reflectivity
|
O.prototype.material.reflectivity = I.prototype.material.reflectivity
|
||||||
S.prototype.material.reflectivity = I.prototype.material.reflectivity
|
S.prototype.material.reflectivity = I.prototype.material.reflectivity
|
||||||
T.prototype.material.reflectivity = I.prototype.material.reflectivity
|
T.prototype.material.reflectivity = I.prototype.material.reflectivity
|
||||||
Z.prototype.material.reflectivity = I.prototype.material.reflectivity
|
Z.prototype.material.reflectivity = I.prototype.material.reflectivity
|
||||||
})*/
|
})
|
||||||
|
|
||||||
this.fps = new FPS.default()
|
this.fps = new FPS.default()
|
||||||
document.body.appendChild(this.fps.dom)
|
document.body.appendChild(this.fps.dom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
|
64
jsm/Vortex.js
Normal file
64
jsm/Vortex.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import * as THREE from 'three'
|
||||||
|
|
||||||
|
|
||||||
|
const GLOBAL_ROTATION = 0.028
|
||||||
|
|
||||||
|
const darkTextureRotation = 0.006
|
||||||
|
const darkMoveForward = 0.007
|
||||||
|
|
||||||
|
const colorFullTextureRotation = 0.006
|
||||||
|
const colorFullMoveForward = 0.02
|
||||||
|
|
||||||
|
|
||||||
|
class Vortex extends THREE.Group {
|
||||||
|
constructor(loadingManager) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
const commonCylinderGeometry = new THREE.CylinderGeometry(25, 25, 500, 12, 1, true)
|
||||||
|
|
||||||
|
this.darkCylinder = new THREE.Mesh(
|
||||||
|
commonCylinderGeometry,
|
||||||
|
new THREE.MeshLambertMaterial({
|
||||||
|
side: THREE.BackSide,
|
||||||
|
map: new THREE.TextureLoader(loadingManager).load("images/plasma.jpg", (texture) => {
|
||||||
|
texture.wrapS = THREE.RepeatWrapping
|
||||||
|
texture.wrapT = THREE.MirroredRepeatWrapping
|
||||||
|
texture.repeat.set(1, 1)
|
||||||
|
}),
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
opacity: 0.1
|
||||||
|
})
|
||||||
|
)
|
||||||
|
this.darkCylinder.position.set(5, 10, -10)
|
||||||
|
this.add(this.darkCylinder)
|
||||||
|
|
||||||
|
this.colorFullCylinder = new THREE.Mesh(
|
||||||
|
commonCylinderGeometry,
|
||||||
|
new THREE.MeshBasicMaterial({
|
||||||
|
side: THREE.BackSide,
|
||||||
|
map: new THREE.TextureLoader(loadingManager).load("images/plasma2.jpg", (texture) => {
|
||||||
|
texture.wrapS = THREE.RepeatWrapping
|
||||||
|
texture.wrapT = THREE.MirroredRepeatWrapping
|
||||||
|
texture.repeat.set(2, 1)
|
||||||
|
}),
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
opacity: 0.6
|
||||||
|
})
|
||||||
|
)
|
||||||
|
this.colorFullCylinder.position.set(5, 10, -10)
|
||||||
|
this.add(this.colorFullCylinder)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(delta) {
|
||||||
|
this.darkCylinder.rotation.y += GLOBAL_ROTATION * delta
|
||||||
|
this.darkCylinder.material.map.offset.y += darkMoveForward * delta
|
||||||
|
this.darkCylinder.material.map.offset.x += darkTextureRotation * delta
|
||||||
|
|
||||||
|
this.colorFullCylinder.rotation.y += GLOBAL_ROTATION * delta
|
||||||
|
this.colorFullCylinder.material.map.offset.y += colorFullMoveForward * delta
|
||||||
|
this.colorFullCylinder.material.map.offset.x += colorFullTextureRotation * delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export { Vortex }
|
@ -1,28 +0,0 @@
|
|||||||
const DELAY = {
|
|
||||||
LOCK: 500,
|
|
||||||
FALL: 1000,
|
|
||||||
}
|
|
||||||
|
|
||||||
const T_SPIN = {
|
|
||||||
NONE: "",
|
|
||||||
MINI: "PETITE<br/>PIROUETTE",
|
|
||||||
T_SPIN: "PIROUETTE"
|
|
||||||
}
|
|
||||||
|
|
||||||
// score = AWARDED_LINE_CLEARS[tSpin][nbClearedLines]
|
|
||||||
const AWARDED_LINE_CLEARS = {
|
|
||||||
[T_SPIN.NONE]: [0, 1, 3, 5, 8],
|
|
||||||
[T_SPIN.MINI]: [1, 2],
|
|
||||||
[T_SPIN.T_SPIN]: [4, 8, 12, 16]
|
|
||||||
}
|
|
||||||
|
|
||||||
const CLEARED_LINES_NAMES = [
|
|
||||||
"",
|
|
||||||
"SOLO",
|
|
||||||
"DUO",
|
|
||||||
"TRIO",
|
|
||||||
"TETRA",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
export { DELAY, T_SPIN, AWARDED_LINE_CLEARS, CLEARED_LINES_NAMES }
|
|
493
jsm/gamelogic.js
Normal file
493
jsm/gamelogic.js
Normal file
@ -0,0 +1,493 @@
|
|||||||
|
import * as THREE from 'three'
|
||||||
|
import { scheduler } from './scheduler.js'
|
||||||
|
|
||||||
|
|
||||||
|
let P = (x, y, z = 0) => new THREE.Vector3(x, y, z)
|
||||||
|
|
||||||
|
const GRAVITY = -20
|
||||||
|
|
||||||
|
const COLORS = {
|
||||||
|
I: 0xafeff9,
|
||||||
|
J: 0xb8b4ff,
|
||||||
|
L: 0xfdd0b7,
|
||||||
|
O: 0xffedac,
|
||||||
|
S: 0xC8FBA8,
|
||||||
|
T: 0xedb2ff,
|
||||||
|
Z: 0xffb8c5,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TRANSLATION = {
|
||||||
|
NONE : P( 0, 0),
|
||||||
|
LEFT : P(-1, 0),
|
||||||
|
RIGHT: P( 1, 0),
|
||||||
|
UP : P( 0, 1),
|
||||||
|
DOWN : P( 0, -1),
|
||||||
|
}
|
||||||
|
|
||||||
|
const ROTATION = {
|
||||||
|
CW: 1, // ClockWise
|
||||||
|
CCW: 3, // CounterClockWise
|
||||||
|
}
|
||||||
|
|
||||||
|
const T_SPIN = {
|
||||||
|
NONE: "",
|
||||||
|
MINI: "PETITE<br/>PIROUETTE",
|
||||||
|
T_SPIN: "PIROUETTE"
|
||||||
|
}
|
||||||
|
|
||||||
|
const FACING = {
|
||||||
|
NORTH: 0,
|
||||||
|
EAST: 1,
|
||||||
|
SOUTH: 2,
|
||||||
|
WEST: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const envRenderTarget = new THREE.WebGLCubeRenderTarget(256)
|
||||||
|
const environnement = envRenderTarget.texture
|
||||||
|
environnement.type = THREE.HalfFloatType
|
||||||
|
environnement.camera = new THREE.CubeCamera(1, 1000, envRenderTarget)
|
||||||
|
environnement.camera.position.set(5, 10)
|
||||||
|
|
||||||
|
|
||||||
|
class Mino extends THREE.Mesh {
|
||||||
|
constructor() {
|
||||||
|
super(Mino.prototype.geometry)
|
||||||
|
this.velocity = P(50 - 100 * Math.random(), 50 - 100 * Math.random(), 50 - 100 * Math.random())
|
||||||
|
this.rotationAngle = P(Math.random(), Math.random(), Math.random()).normalize()
|
||||||
|
this.angularVelocity = 5 - 10 * Math.random()
|
||||||
|
}
|
||||||
|
|
||||||
|
update(delta) {
|
||||||
|
this.velocity.y += delta * GRAVITY
|
||||||
|
this.position.addScaledVector(this.velocity, delta)
|
||||||
|
this.rotateOnWorldAxis(this.rotationAngle, delta * this.angularVelocity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const minoFaceShape = new THREE.Shape()
|
||||||
|
minoFaceShape.moveTo(.1, .1)
|
||||||
|
minoFaceShape.lineTo(.1, .9)
|
||||||
|
minoFaceShape.lineTo(.9, .9)
|
||||||
|
minoFaceShape.lineTo(.9, .1)
|
||||||
|
minoFaceShape.lineTo(.1, .1)
|
||||||
|
const minoExtrudeSettings = {
|
||||||
|
steps: 1,
|
||||||
|
depth: .8,
|
||||||
|
bevelEnabled: true,
|
||||||
|
bevelThickness: .1,
|
||||||
|
bevelSize: .1,
|
||||||
|
bevelOffset: 0,
|
||||||
|
bevelSegments: 1
|
||||||
|
}
|
||||||
|
Mino.prototype.geometry = new THREE.ExtrudeGeometry(minoFaceShape, minoExtrudeSettings)
|
||||||
|
|
||||||
|
|
||||||
|
class MinoMaterial extends THREE.MeshBasicMaterial {
|
||||||
|
constructor(color) {
|
||||||
|
super({
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
color: color,
|
||||||
|
envMap: environnement,
|
||||||
|
reflectivity: 0.9,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GhostMaterial extends THREE.MeshBasicMaterial {
|
||||||
|
constructor(color) {
|
||||||
|
super({
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
color: color,
|
||||||
|
envMap: environnement,
|
||||||
|
reflectivity: 0.9,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractTetromino extends THREE.Group {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
this.add(new Mino())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set facing(facing) {
|
||||||
|
this._facing = facing
|
||||||
|
this.minoesPosition[this.facing].forEach(
|
||||||
|
(position, i) => this.children[i].position.copy(position)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get facing() {
|
||||||
|
return this._facing
|
||||||
|
}
|
||||||
|
|
||||||
|
canMove(translation, facing=this.facing) {
|
||||||
|
let testPosition = this.position.clone().add(translation)
|
||||||
|
return this.minoesPosition[facing].every(minoPosition => this.parent.cellIsEmpty(minoPosition.clone().add(testPosition)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ghost extends AbstractTetromino {}
|
||||||
|
Ghost.prototype.minoesPosition = [
|
||||||
|
[P(0, 0, 0), P(0, 0, 0), P(0, 0, 0), P(0, 0, 0)],
|
||||||
|
]
|
||||||
|
|
||||||
|
class Tetromino extends AbstractTetromino {
|
||||||
|
static randomBag = []
|
||||||
|
static get random() {
|
||||||
|
if (!this.randomBag.length) this.randomBag = [I, J, L, O, S, T, Z]
|
||||||
|
return this.randomBag.pick()
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.rotatedLast = false
|
||||||
|
this.rotationPoint4Used = false
|
||||||
|
this.holdEnabled = true
|
||||||
|
this.facing = 0
|
||||||
|
this.locking = false
|
||||||
|
}
|
||||||
|
|
||||||
|
move(translation, rotatedFacing, rotationPoint) {
|
||||||
|
if (this.canMove(translation, rotatedFacing)) {
|
||||||
|
this.position.add(translation)
|
||||||
|
this.rotatedLast = rotatedFacing
|
||||||
|
if (rotatedFacing != undefined) {
|
||||||
|
this.facing = rotatedFacing
|
||||||
|
if (rotationPoint == 4) this.rotationPoint4Used = true
|
||||||
|
}
|
||||||
|
if (this.canMove(TRANSLATION.DOWN)) {
|
||||||
|
this.locking = false
|
||||||
|
scheduler.clearTimeout(this.onlockdown)
|
||||||
|
} else {
|
||||||
|
scheduler.resetTimeout(this.onlockdown, this.lockDelay)
|
||||||
|
this.locking = true
|
||||||
|
}
|
||||||
|
if (this.ghost.visible) this.updateGhost()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rotate(rotation) {
|
||||||
|
let testFacing = (this.facing + rotation) % 4
|
||||||
|
return this.srs[this.facing][rotation].some(
|
||||||
|
(translation, rotationPoint) => this.move(translation, testFacing, rotationPoint)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
set locking(locking) {
|
||||||
|
if (locking) {
|
||||||
|
this.children.forEach(mino => mino.material = this.lockedMaterial)
|
||||||
|
this.ghost.visible = false
|
||||||
|
} else {
|
||||||
|
this.children.forEach(mino => mino.material = this.material)
|
||||||
|
this.ghost.visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGhost() {
|
||||||
|
this.ghost.position.copy(this.position)
|
||||||
|
this.ghost.minoesPosition = this.minoesPosition
|
||||||
|
this.ghost.facing = this.facing
|
||||||
|
while (this.ghost.canMove(TRANSLATION.DOWN)) this.ghost.position.y--
|
||||||
|
}
|
||||||
|
|
||||||
|
get tSpin() {
|
||||||
|
return T_SPIN.NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Super Rotation System
|
||||||
|
// freedom of movement = srs[this.parent.piece.facing][rotation]
|
||||||
|
Tetromino.prototype.srs = [
|
||||||
|
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(-1, 1), P(0, -2), P(-1, -2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(1, 1), P(0, -2), P(1, -2)] },
|
||||||
|
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(1, -1), P(0, 2), P(1, 2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(1, -1), P(0, 2), P(1, 2)] },
|
||||||
|
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(1, 1), P(0, -2), P(1, -2)], [ROTATION.CCW]: [P(0, 0), P(-1, 0), P(-1, 1), P(0, -2), P(-1, -2)] },
|
||||||
|
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(-1, -1), P(0, 2), P(-1, 2)], [ROTATION.CCW]: [P(0, 0), P(-1, 0), P(-1, -1), P(0, 2), P(-1, 2)] },
|
||||||
|
]
|
||||||
|
Tetromino.prototype.lockedMaterial = new MinoMaterial(0xffffff)
|
||||||
|
Tetromino.prototype.lockDelay = 500
|
||||||
|
Tetromino.prototype.ghost = new Ghost()
|
||||||
|
|
||||||
|
|
||||||
|
class I extends Tetromino { }
|
||||||
|
I.prototype.minoesPosition = [
|
||||||
|
[P(-1, 0), P(0, 0), P(1, 0), P(2, 0)],
|
||||||
|
[P(1, 1), P(1, 0), P(1, -1), P(1, -2)],
|
||||||
|
[P(-1, -1), P(0, -1), P(1, -1), P(2, -1)],
|
||||||
|
[P(0, 1), P(0, 0), P(0, -1), P(0, -2)],
|
||||||
|
]
|
||||||
|
I.prototype.srs = [
|
||||||
|
{ [ROTATION.CW]: [P(0, 0), P(-2, 0), P(1, 0), P(-2, -1), P(1, 2)], [ROTATION.CCW]: [P(0, 0), P(-1, 0), P(2, 0), P(-1, 2), P(2, -1)] },
|
||||||
|
{ [ROTATION.CW]: [P(0, 0), P(-1, 0), P(2, 0), P(-1, 2), P(2, -1)], [ROTATION.CCW]: [P(0, 0), P(2, 0), P(-1, 0), P(2, 1), P(-1, -2)] },
|
||||||
|
{ [ROTATION.CW]: [P(0, 0), P(2, 0), P(-1, 0), P(2, 1), P(-1, -2)], [ROTATION.CCW]: [P(0, 0), P(1, 0), P(-2, 0), P(1, -2), P(-2, 1)] },
|
||||||
|
{ [ROTATION.CW]: [P(0, 0), P(1, 0), P(-2, 0), P(1, -2), P(-2, 1)], [ROTATION.CCW]: [P(0, 0), P(-2, 0), P(1, 0), P(-2, -1), P(1, 2)] },
|
||||||
|
]
|
||||||
|
I.prototype.material = new MinoMaterial(COLORS.I)
|
||||||
|
I.prototype.ghostMaterial = new GhostMaterial(COLORS.I)
|
||||||
|
|
||||||
|
class J extends Tetromino { }
|
||||||
|
J.prototype.minoesPosition = [
|
||||||
|
[P(-1, 1), P(-1, 0), P(0, 0), P(1, 0)],
|
||||||
|
[P(0, 1), P(1, 1), P(0, 0), P(0, -1)],
|
||||||
|
[P(1, -1), P(-1, 0), P(0, 0), P(1, 0)],
|
||||||
|
[P(0, 1), P(-1, -1), P(0, 0), P(0, -1)],
|
||||||
|
]
|
||||||
|
J.prototype.material = new MinoMaterial(COLORS.J)
|
||||||
|
J.prototype.ghostMaterial = new GhostMaterial(COLORS.J)
|
||||||
|
|
||||||
|
class L extends Tetromino { }
|
||||||
|
L.prototype.minoesPosition = [
|
||||||
|
[P(-1, 0), P(0, 0), P(1, 0), P(1, 1)],
|
||||||
|
[P(0, 1), P(0, 0), P(0, -1), P(1, -1)],
|
||||||
|
[P(-1, 0), P(0, 0), P(1, 0), P(-1, -1)],
|
||||||
|
[P(0, 1), P(0, 0), P(0, -1), P(-1, 1)],
|
||||||
|
]
|
||||||
|
L.prototype.material = new MinoMaterial(COLORS.L)
|
||||||
|
L.prototype.ghostMaterial = new GhostMaterial(COLORS.L)
|
||||||
|
|
||||||
|
class O extends Tetromino { }
|
||||||
|
O.prototype.minoesPosition = [
|
||||||
|
[P(0, 0), P(1, 0), P(0, 1), P(1, 1)]
|
||||||
|
]
|
||||||
|
O.prototype.srs = [
|
||||||
|
{ [ROTATION.CW]: [], [ROTATION.CCW]: [] }
|
||||||
|
]
|
||||||
|
O.prototype.material = new MinoMaterial(COLORS.O)
|
||||||
|
O.prototype.ghostMaterial = new GhostMaterial(COLORS.O)
|
||||||
|
|
||||||
|
class S extends Tetromino { }
|
||||||
|
S.prototype.minoesPosition = [
|
||||||
|
[P(-1, 0), P(0, 0), P(0, 1), P(1, 1)],
|
||||||
|
[P(0, 1), P(0, 0), P(1, 0), P(1, -1)],
|
||||||
|
[P(-1, -1), P(0, 0), P(1, 0), P(0, -1)],
|
||||||
|
[P(-1, 1), P(0, 0), P(-1, 0), P(0, -1)],
|
||||||
|
]
|
||||||
|
S.prototype.material = new MinoMaterial(COLORS.S)
|
||||||
|
S.prototype.ghostMaterial = new GhostMaterial(COLORS.S)
|
||||||
|
|
||||||
|
class T extends Tetromino {
|
||||||
|
get tSpin() {
|
||||||
|
if (this.rotatedLast) {
|
||||||
|
let [a, b, c, d] = this.tSlots[this.facing]
|
||||||
|
.map(p => !this.parent.cellIsEmpty(p.clone().add(this.position)))
|
||||||
|
if (a && b && (c || d))
|
||||||
|
return T_SPIN.T_SPIN
|
||||||
|
else if (c && d && (a || b))
|
||||||
|
return this.rotationPoint4Used ? T_SPIN.T_SPIN : T_SPIN.MINI
|
||||||
|
}
|
||||||
|
return T_SPIN.NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
T.prototype.minoesPosition = [
|
||||||
|
[P(-1, 0), P(0, 0), P(1, 0), P(0, 1)],
|
||||||
|
[P(0, 1), P(0, 0), P(1, 0), P(0, -1)],
|
||||||
|
[P(-1, 0), P(0, 0), P(1, 0), P(0, -1)],
|
||||||
|
[P(0, 1), P(0, 0), P(0, -1), P(-1, 0)],
|
||||||
|
]
|
||||||
|
T.prototype.tSlots = [
|
||||||
|
[P(-1, 1), P(1, 1), P(1, -1), P(-1, -1)],
|
||||||
|
[P(1, 1), P(1, -1), P(-1, -1), P(-1, 1)],
|
||||||
|
[P(1, -1), P(-1, -1), P(-1, 1), P(1, 1)],
|
||||||
|
[P(-1, -1), P(-1, 1), P(1, 1), P(1, -1)],
|
||||||
|
]
|
||||||
|
T.prototype.material = new MinoMaterial(COLORS.T)
|
||||||
|
T.prototype.ghostMaterial = new GhostMaterial(COLORS.T)
|
||||||
|
|
||||||
|
class Z extends Tetromino { }
|
||||||
|
Z.prototype.minoesPosition = [
|
||||||
|
[P(-1, 1), P(0, 1), P(0, 0), P(1, 0)],
|
||||||
|
[P(1, 1), P(1, 0), P(0, 0), P(0, -1)],
|
||||||
|
[P(-1, 0), P(0, 0), P(0, -1), P(1, -1)],
|
||||||
|
[P(0, 1), P(-1, 0), P(0, 0), P(-1, -1)]
|
||||||
|
]
|
||||||
|
Z.prototype.material = new MinoMaterial(COLORS.Z)
|
||||||
|
Z.prototype.ghostMaterial = new GhostMaterial(COLORS.Z)
|
||||||
|
|
||||||
|
|
||||||
|
const ROWS = 24
|
||||||
|
const SKYLINE = 20
|
||||||
|
const COLUMNS = 10
|
||||||
|
|
||||||
|
|
||||||
|
class Matrix extends THREE.Group {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.visible = false
|
||||||
|
|
||||||
|
const edgeMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x88abe0,
|
||||||
|
envMap: environnement,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.4,
|
||||||
|
reflectivity: 0.9,
|
||||||
|
refractionRatio: 0.5
|
||||||
|
})
|
||||||
|
const edgeShape = new THREE.Shape()
|
||||||
|
.moveTo(-.3, SKYLINE)
|
||||||
|
.lineTo(0, SKYLINE)
|
||||||
|
.lineTo(0, 0)
|
||||||
|
.lineTo(COLUMNS, 0)
|
||||||
|
.lineTo(COLUMNS, SKYLINE)
|
||||||
|
.lineTo(COLUMNS + .3, SKYLINE)
|
||||||
|
.lineTo(COLUMNS + .3, -.3)
|
||||||
|
.lineTo(-.3, -.3)
|
||||||
|
.moveTo(-.3, SKYLINE)
|
||||||
|
const edge = new THREE.Mesh(
|
||||||
|
new THREE.ExtrudeGeometry(edgeShape, {
|
||||||
|
depth: 1,
|
||||||
|
bevelEnabled: false,
|
||||||
|
}),
|
||||||
|
edgeMaterial
|
||||||
|
)
|
||||||
|
this.add(edge)
|
||||||
|
|
||||||
|
const positionKF = new THREE.VectorKeyframeTrack('.position', [0, 1, 2], [0, 0, 0, 0, -0.2, 0, 0, 0, 0])
|
||||||
|
const clip = new THREE.AnimationClip('HardDrop', 3, [positionKF])
|
||||||
|
const animationGroup = new THREE.AnimationObjectGroup()
|
||||||
|
animationGroup.add(this)
|
||||||
|
this.mixer = new THREE.AnimationMixer(animationGroup)
|
||||||
|
this.hardDropAnimation = this.mixer.clipAction(clip)
|
||||||
|
this.hardDropAnimation.loop = THREE.LoopOnce
|
||||||
|
this.hardDropAnimation.setDuration(0.2)
|
||||||
|
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
while(this.children.length > 1 ) this.remove(this.children[1])
|
||||||
|
this.cells = Array(ROWS).fill().map(() => Array(COLUMNS))
|
||||||
|
this.unlockedMinoes = new Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
cellIsEmpty(p) {
|
||||||
|
return 0 <= p.x && p.x < COLUMNS &&
|
||||||
|
0 <= p.y && p.y < ROWS &&
|
||||||
|
!this.cells[p.y][p.x]
|
||||||
|
}
|
||||||
|
|
||||||
|
set piece(piece) {
|
||||||
|
if (piece) {
|
||||||
|
this.add(piece)
|
||||||
|
piece.position.set(4, SKYLINE)
|
||||||
|
this.add(piece.ghost)
|
||||||
|
piece.ghost.children.forEach((mino) => {
|
||||||
|
mino.material = piece.ghostMaterial
|
||||||
|
})
|
||||||
|
piece.updateGhost()
|
||||||
|
}
|
||||||
|
this._piece = piece
|
||||||
|
}
|
||||||
|
|
||||||
|
get piece() {
|
||||||
|
return this._piece
|
||||||
|
}
|
||||||
|
|
||||||
|
lock() {
|
||||||
|
this.piece.locking = false
|
||||||
|
let minoes = Array.from(this.piece.children)
|
||||||
|
minoes.forEach(mino => {
|
||||||
|
mino.position.add(this.piece.position)
|
||||||
|
this.add(mino)
|
||||||
|
if (this.cellIsEmpty(mino.position)) {
|
||||||
|
this.cells[mino.position.y][mino.position.x] = mino
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return minoes.some(mino => mino.position.y < SKYLINE)
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLines() {
|
||||||
|
let nbClearedLines = this.cells.reduceRight((nbClearedLines, row, y) => {
|
||||||
|
if (row.filter(mino => mino).length == COLUMNS) {
|
||||||
|
row.forEach(mino => this.unlockedMinoes.add(mino))
|
||||||
|
this.cells.splice(y, 1)
|
||||||
|
this.cells.push(Array(COLUMNS))
|
||||||
|
return ++nbClearedLines
|
||||||
|
}
|
||||||
|
return nbClearedLines
|
||||||
|
}, 0)
|
||||||
|
if (nbClearedLines) {
|
||||||
|
this.cells.forEach((rows, y) => {
|
||||||
|
rows.forEach((mino, x) => {
|
||||||
|
mino.position.set(x, y)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nbClearedLines
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUnlockedMinoes(delta) {
|
||||||
|
this.unlockedMinoes.forEach(mino => {
|
||||||
|
mino.update(delta)
|
||||||
|
if (Math.sqrt(mino.position.x * mino.position.x + mino.position.z * mino.position.z) > 25) {
|
||||||
|
this.remove(mino)
|
||||||
|
this.unlockedMinoes.delete(mino)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update(delta) {
|
||||||
|
this.updateUnlockedMinoes(delta)
|
||||||
|
this.mixer?.update(delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HoldQueue extends THREE.Group {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.position.set(-4, SKYLINE - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
set piece(piece) {
|
||||||
|
if(piece) {
|
||||||
|
piece.holdEnabled = false
|
||||||
|
piece.locking = false
|
||||||
|
piece.position.set(0, 0)
|
||||||
|
piece.facing = FACING.NORTH
|
||||||
|
this.add(piece)
|
||||||
|
}
|
||||||
|
this._piece = piece
|
||||||
|
}
|
||||||
|
|
||||||
|
get piece() {
|
||||||
|
return this._piece
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NextQueue extends THREE.Group {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.position.set(13, SKYLINE - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.pieces = this.positions.map((position) => {
|
||||||
|
let piece = new Tetromino.random()
|
||||||
|
piece.position.copy(position)
|
||||||
|
this.add(piece)
|
||||||
|
return piece
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
shift() {
|
||||||
|
let fistPiece = this.pieces.shift()
|
||||||
|
let lastPiece = new Tetromino.random()
|
||||||
|
this.add(lastPiece)
|
||||||
|
this.pieces.push(lastPiece)
|
||||||
|
this.positions.forEach((position, i) => {
|
||||||
|
this.pieces[i].position.copy(position)
|
||||||
|
})
|
||||||
|
return fistPiece
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
NextQueue.prototype.positions = [P(0, 0), P(0, -3), P(0, -6), P(0, -9), P(0, -12), P(0, -15), P(0, -18)]
|
||||||
|
|
||||||
|
|
||||||
|
export { T_SPIN, FACING, TRANSLATION, ROTATION, environnement, Tetromino, I, J, L, O, S, T, Z, Matrix, HoldQueue, NextQueue }
|
39
jsm/scheduler.js
Normal file
39
jsm/scheduler.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
class Scheduler {
|
||||||
|
constructor() {
|
||||||
|
this.intervalTasks = new Map()
|
||||||
|
this.timeoutTasks = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(func, delay, ...args) {
|
||||||
|
this.intervalTasks.set(func, window.setInterval(func, delay, ...args))
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInterval(func) {
|
||||||
|
if (this.intervalTasks.has(func)) {
|
||||||
|
window.clearInterval(this.intervalTasks.get(func))
|
||||||
|
this.intervalTasks.delete(func)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(func, delay, ...args) {
|
||||||
|
this.timeoutTasks.set(func, window.setTimeout(func, delay, ...args))
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(func) {
|
||||||
|
if (this.timeoutTasks.has(func)) {
|
||||||
|
window.clearTimeout(this.timeoutTasks.get(func))
|
||||||
|
this.timeoutTasks.delete(func)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTimeout(func, delay, ...args) {
|
||||||
|
this.clearTimeout(func)
|
||||||
|
this.setTimeout(func, delay, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const scheduler = new Scheduler
|
||||||
|
|
||||||
|
|
||||||
|
export { scheduler }
|
36
jsm/utils.js
36
jsm/utils.js
@ -1,36 +0,0 @@
|
|||||||
class Scheduler {
|
|
||||||
constructor() {
|
|
||||||
this.intervalTasks = new Map()
|
|
||||||
this.timeoutTasks = new Map()
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(func, delay, ...args) {
|
|
||||||
this.intervalTasks.set(func, window.setInterval(func, delay, ...args))
|
|
||||||
}
|
|
||||||
|
|
||||||
clearInterval(func) {
|
|
||||||
if (this.intervalTasks.has(func)) {
|
|
||||||
window.clearInterval(this.intervalTasks.get(func))
|
|
||||||
this.intervalTasks.delete(func)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(func, delay, ...args) {
|
|
||||||
this.timeoutTasks.set(func, window.setTimeout(func, delay, ...args))
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(func) {
|
|
||||||
if (this.timeoutTasks.has(func)) {
|
|
||||||
window.clearTimeout(this.timeoutTasks.get(func))
|
|
||||||
this.timeoutTasks.delete(func)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resetTimeout(func, delay, ...args) {
|
|
||||||
this.clearTimeout(func)
|
|
||||||
this.setTimeout(func, delay, ...args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export { Scheduler }
|
|
Loading…
x
Reference in New Issue
Block a user