This commit is contained in:
Adrien MALINGREY 2019-11-03 15:08:58 +01:00
parent e39216f7bf
commit 01cecac2a5
2 changed files with 88 additions and 69 deletions

View File

@ -47,6 +47,7 @@
.cleared-line { .cleared-line {
background: white; background: white;
transition: background 1s;
} }
.trail { .trail {

View File

@ -14,32 +14,46 @@ Array.prototype.rotate = function(spin) { return [-spin*this.y, spin*this.x
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] }
// Constants // Constants
const NEXT_PIECES = 6 const NEXT_PIECES = 6
const HOLD_ROWS = 6 const HOLD = {
const HOLD_COLUMNS = 6 ROWS: 6,
const MATRIX_ROWS = 24 COLUMNS: 6
const MATRIX_INVISIBLE_ROWS = 4 }
const MATRIX_COLUMNS = 10 const MATRIX = {
const NEXT_ROWS = 24 ROWS: 24,
const NEXT_COLUMNS = 6 INVISIBLE_ROWS: 4,
const THEME_ROWS = 6 COLUMNS: 10
const THEME_COLUMNS = 6 }
const EMPTY_CELL_CLASS = "empty-cell" const NEXT= {
const MINO_CLASS = "mino" ROWS: 24,
const LOCKED_PIECE_CLASS = "locked-mino" COLUMNS: 6
const TRAIL_CLASS = "trail" }
const GHOST_CLASS = "ghost" const THEME = {
const CLEARED_LINE_CLASS = "mino cleared-line" ROWS: 6,
const HELD_PIECE_POSITION = [2, 3] COLUMNS: 6
const FALLING_PIECE_POSITION = [4, 3] }
const NEXT_PIECES_POSITIONS = Array.from({length: NEXT_PIECES}, (v, k) => [2, k*4+3]) const CLASSNAME = {
const THEME_PIECE_POSITION = [2, 3] EMPTY_CELL: "empty-cell",
const LOCK_DELAY = 500 MINO: "mino",
const FALL_PERIOD = 1000 LOCKED: "locked-mino",
const AUTOREPEAT_DELAY = 300 TRAIL: "trail",
const AUTOREPEAT_PERIOD = 10 GHOST: "ghost",
const ANIMATION_DELAY = 100 CLEARED_LINE: "mino cleared-line"
const TEMP_TEXTS_DELAY = 700 }
const POSITION = {
HELD_PIECE: [2, 3],
FALLING_PIECE: [4, 3],
NEXT_PIECES: Array.from({length: NEXT_PIECES}, (v, k) => [2, k*4+3]),
THEMED_PIECE: [2, 3]
}
const DELAY = {
LOCK: 500,
FALL: 1000,
AUTOREPEAT_DELAY: 300,
AUTOREPEAT_PERIOD: 10,
ANIMATION: 100,
MESSAGE: 700
}
const MOVEMENT = { const MOVEMENT = {
LEFT: [-1, 0], LEFT: [-1, 0],
RIGHT: [ 1, 0], RIGHT: [ 1, 0],
@ -60,7 +74,11 @@ const T_SLOT = {
C: 3, C: 3,
D: 2 D: 2
} }
const T_SLOT_POS = [[-1, -1], [1, -1], [1, 1], [-1, 1]] const T_SLOT_POS = [
[-1, -1],
[1, -1],
[1, 1],[-1, 1]
]
const SCORES = [ const SCORES = [
{linesClearedName: "", "": 0, "MINI\nT-SPIN": 1, "T-SPIN": 4}, {linesClearedName: "", "": 0, "MINI\nT-SPIN": 1, "T-SPIN": 4},
{linesClearedName: "SINGLE", "": 1, "MINI\nT-SPIN": 2, "T-SPIN": 8}, {linesClearedName: "SINGLE", "": 1, "MINI\nT-SPIN": 2, "T-SPIN": 8},
@ -186,7 +204,7 @@ class Tetromino {
this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]] this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]]
break break
} }
this.className = MINO_CLASS + " " + this.shape + "-" + MINO_CLASS this.className = CLASSNAME.MINO + " " + this.shape + "-" + CLASSNAME.MINO
} }
get minoesAbsPos() { get minoesAbsPos() {
@ -196,7 +214,7 @@ class Tetromino {
get ghost() { get ghost() {
var ghost = new Tetromino(Array.from(this.pos), this.shape) var ghost = new Tetromino(Array.from(this.pos), this.shape)
ghost.minoesPos = Array.from(this.minoesPos) ghost.minoesPos = Array.from(this.minoesPos)
ghost.className = GHOST_CLASS ghost.className = CLASSNAME.GHOST
return ghost return ghost
} }
} }
@ -213,15 +231,14 @@ class MinoesTable {
this.table.rows[y].cells[x].className = className this.table.rows[y].cells[x].className = className
} }
drawPiece(piece) { drawPiece(piece=this.piece, className=piece.locked ? CLASSNAME.LOCKED + " "+ piece.className: piece.className) {
var className = piece.locked ? LOCKED_PIECE_CLASS + " "+ piece.className: piece.className
piece.minoesAbsPos.forEach(pos => this.drawMino(...pos, className)) piece.minoesAbsPos.forEach(pos => this.drawMino(...pos, className))
} }
clearTable() { clearTable() {
for(var y = 0; y < this.rows; y++) { for(var y = 0; y < this.rows; y++) {
for (var x = 0; x < this.columns; x++) { for (var x = 0; x < this.columns; x++) {
this.drawMino(x, y, EMPTY_CELL_CLASS) this.drawMino(x, y, CLASSNAME.EMPTY_CELL)
} }
} }
} }
@ -229,7 +246,7 @@ class MinoesTable {
class HoldQueue extends MinoesTable { class HoldQueue extends MinoesTable {
constructor() { constructor() {
super("hold", HOLD_ROWS, HOLD_COLUMNS) super("hold", HOLD.ROWS, HOLD.COLUMNS)
} }
newGame() { newGame() {
@ -246,11 +263,11 @@ class HoldQueue extends MinoesTable {
class Matrix extends MinoesTable { class Matrix extends MinoesTable {
constructor() { constructor() {
super("matrix", MATRIX_ROWS, MATRIX_COLUMNS) super("matrix", MATRIX.ROWS, MATRIX.COLUMNS)
} }
newGame() { newGame() {
this.lockedMinoes = Array.from(Array(MATRIX_ROWS+3), row => Array(MATRIX_COLUMNS)) this.lockedMinoes = Array.from(Array(MATRIX.ROWS+3), row => Array(MATRIX.COLUMNS))
this.piece = null this.piece = null
this.clearedLines = [] this.clearedLines = []
this.trail = { this.trail = {
@ -260,7 +277,7 @@ class Matrix extends MinoesTable {
} }
cellIsOccupied(x, y) { cellIsOccupied(x, y) {
return 0 <= x && x < MATRIX_COLUMNS && y < MATRIX_ROWS ? this.lockedMinoes[y][x] : true return 0 <= x && x < MATRIX.COLUMNS && y < MATRIX.ROWS ? this.lockedMinoes[y][x] : true
} }
spaceToMove(minoesAbsPos) { spaceToMove(minoesAbsPos) {
@ -277,9 +294,9 @@ class Matrix extends MinoesTable {
var className = this.lockedMinoes[y][x] var className = this.lockedMinoes[y][x]
if (!className) { if (!className) {
if (this.clearedLines.includes(y)) if (this.clearedLines.includes(y))
className = CLEARED_LINE_CLASS className = CLASSNAME.CLEARED_LINE
else else
className = EMPTY_CELL_CLASS className = CLASSNAME.EMPTY_CELL
} }
this.drawMino(x, y, className) this.drawMino(x, y, className)
} }
@ -289,7 +306,7 @@ class Matrix extends MinoesTable {
if (this.trail.height) { if (this.trail.height) {
this.trail.minoesPos.forEach(pos => { this.trail.minoesPos.forEach(pos => {
for (var y = pos.y; y < pos.y + this.trail.height; y++) for (var y = pos.y; y < pos.y + this.trail.height; y++)
this.drawMino(pos.x, y, TRAIL_CLASS) this.drawMino(pos.x, y, CLASSNAME.TRAIL)
}) })
} }
@ -308,11 +325,11 @@ class Matrix extends MinoesTable {
class NextQueue extends MinoesTable { class NextQueue extends MinoesTable {
constructor() { constructor() {
super("next", NEXT_ROWS, NEXT_COLUMNS) super("next", NEXT.ROWS, NEXT.COLUMNS)
} }
newGame() { newGame() {
this.pieces = Array.from({length: NEXT_PIECES}, (v, k) => new Tetromino(NEXT_PIECES_POSITIONS[k])) this.pieces = Array.from({length: NEXT_PIECES}, (v, k) => new Tetromino(POSITION.NEXT_PIECES[k]))
} }
draw() { draw() {
@ -326,8 +343,8 @@ class NextQueue extends MinoesTable {
class ThemePreview extends MinoesTable { class ThemePreview extends MinoesTable {
constructor() { constructor() {
super("themePreview", THEME_ROWS, THEME_COLUMNS) super("themePreview", THEME.ROWS, THEME.COLUMNS)
this.piece = new Tetromino(THEME_PIECE_POSITION, "T") this.piece = new Tetromino(POSITION.THEMED_PIECE, "T")
} }
} }
@ -353,8 +370,8 @@ class Stats {
this.time = 0 this.time = 0
this.timeCell.innerText = timeFormat(0) this.timeCell.innerText = timeFormat(0)
this.combo = -1 this.combo = -1
this.lockDelay = LOCK_DELAY this.lockDelay = DELAY.LOCK
this.fallPeriod = FALL_PERIOD this.fallPeriod = DELAY.FALL
} }
get score() { get score() {
@ -460,10 +477,10 @@ function generationPhase(held_piece=null) {
if (!held_piece) { if (!held_piece) {
matrix.piece = nextQueue.pieces.shift() matrix.piece = nextQueue.pieces.shift()
nextQueue.pieces.push(new Tetromino()) nextQueue.pieces.push(new Tetromino())
nextQueue.pieces.forEach((piece, i) => piece.pos = NEXT_PIECES_POSITIONS[i]) nextQueue.pieces.forEach((piece, i) => piece.pos = POSITION.NEXT_PIECES[i])
} }
nextQueue.draw() nextQueue.draw()
matrix.piece.pos = FALLING_PIECE_POSITION matrix.piece.pos = POSITION.FALLING_PIECE
if (matrix.spaceToMove(matrix.piece.minoesPos.translate(matrix.piece.pos))){ if (matrix.spaceToMove(matrix.piece.minoesPos.translate(matrix.piece.pos))){
scheduler.clearInterval(lockPhase) scheduler.clearInterval(lockPhase)
scheduler.setInterval(lockPhase, stats.fallPeriod) scheduler.setInterval(lockPhase, stats.fallPeriod)
@ -528,7 +545,7 @@ function rotate(spin) {
function lockDown(){ function lockDown(){
scheduler.clearInterval(lockPhase) scheduler.clearInterval(lockPhase)
if (matrix.piece.minoesAbsPos.every(pos => pos.y < MATRIX_INVISIBLE_ROWS)) { if (matrix.piece.minoesAbsPos.every(pos => pos.y < MATRIX.INVISIBLE_ROWS)) {
matrix.piece.locked = false matrix.piece.locked = false
matrix.draw() matrix.draw()
gameOver() gameOver()
@ -552,16 +569,16 @@ function lockDown(){
// Complete lines // Complete lines
matrix.clearedLines = [] matrix.clearedLines = []
matrix.lockedMinoes.forEach((row, y) => { matrix.lockedMinoes.forEach((row, y) => {
if (row.filter(lockedMino => lockedMino.length).length == MATRIX_COLUMNS) { if (row.filter(lockedMino => lockedMino.length).length == MATRIX.COLUMNS) {
matrix.lockedMinoes.splice(y, 1) matrix.lockedMinoes.splice(y, 1)
matrix.lockedMinoes.unshift(Array(MATRIX_COLUMNS)) matrix.lockedMinoes.unshift(Array(MATRIX.COLUMNS))
matrix.clearedLines.push(y) matrix.clearedLines.push(y)
} }
}) })
stats.lockDown(tSpin, matrix.clearedLines.length) stats.lockDown(tSpin, matrix.clearedLines.length)
matrix.draw() matrix.draw()
scheduler.setTimeout(clearLinesCleared, ANIMATION_DELAY) scheduler.setTimeout(clearLinesCleared, DELAY.ANIMATION)
if (stats.goal <= 0) if (stats.goal <= 0)
newLevel() newLevel()
@ -637,16 +654,16 @@ function gameOver() {
document.getElementById("leaderboardLink").style.display = "flex" document.getElementById("leaderboardLink").style.display = "flex"
} }
function autorepeat() { function AUTOREPEAT_DELAY() {
if (actionsToRepeat.length) { if (actionsToRepeat.length) {
actionsToRepeat[0]() actionsToRepeat[0]()
if (scheduler.timeoutTasks.has(autorepeat)) { if (scheduler.timeoutTasks.has(AUTOREPEAT_DELAY)) {
scheduler.clearTimeout(autorepeat) scheduler.clearTimeout(AUTOREPEAT_DELAY)
scheduler.setInterval(autorepeat, autorepeatPeriod) scheduler.setInterval(AUTOREPEAT_DELAY, autorepeatPeriod)
} }
} else { } else {
scheduler.clearTimeout(autorepeat) scheduler.clearTimeout(AUTOREPEAT_DELAY)
scheduler.clearInterval(autorepeat) scheduler.clearInterval(AUTOREPEAT_DELAY)
} }
} }
@ -660,12 +677,12 @@ function keyDownHandler(e) {
action() action()
if (REPEATABLE_ACTIONS.includes(action)) { if (REPEATABLE_ACTIONS.includes(action)) {
actionsToRepeat.unshift(action) actionsToRepeat.unshift(action)
scheduler.clearTimeout(autorepeat) scheduler.clearTimeout(AUTOREPEAT_DELAY)
scheduler.clearInterval(autorepeat) scheduler.clearInterval(AUTOREPEAT_DELAY)
if (action == softDrop) if (action == softDrop)
scheduler.setInterval(autorepeat, stats.fallPeriod / 20) scheduler.setInterval(AUTOREPEAT_DELAY, stats.fallPeriod / 20)
else else
scheduler.setTimeout(autorepeat, autorepeatDelay) scheduler.setTimeout(AUTOREPEAT_DELAY, autorepeatDelay)
} }
} }
} }
@ -678,13 +695,14 @@ function keyUpHandler(e) {
if (actionsToRepeat.includes(action)) { if (actionsToRepeat.includes(action)) {
actionsToRepeat.splice(actionsToRepeat.indexOf(action), 1) actionsToRepeat.splice(actionsToRepeat.indexOf(action), 1)
if (!actionsToRepeat.length) { if (!actionsToRepeat.length) {
scheduler.clearTimeout(autorepeat) scheduler.clearTimeout(AUTOREPEAT_DELAY)
scheduler.clearInterval(autorepeat) scheduler.clearInterval(AUTOREPEAT_DELAY)
} }
} }
} }
} }
// actions
function moveLeft() { function moveLeft() {
move(MOVEMENT.LEFT); move(MOVEMENT.LEFT);
} }
@ -706,7 +724,7 @@ function hardDrop() {
stats.score += 2 * matrix.trail.height stats.score += 2 * matrix.trail.height
matrix.draw() matrix.draw()
lockDown() lockDown()
scheduler.setTimeout(clearTrail, ANIMATION_DELAY) scheduler.setTimeout(clearTrail, DELAY.ANIMATION)
} }
function clearTrail() { function clearTrail() {
@ -728,7 +746,7 @@ function hold() {
scheduler.clearInterval(lockDown) scheduler.clearInterval(lockDown)
var shape = matrix.piece.shape var shape = matrix.piece.shape
matrix.piece = holdQueue.piece matrix.piece = holdQueue.piece
holdQueue.piece = new Tetromino(HELD_PIECE_POSITION, shape) holdQueue.piece = new Tetromino(POSITION.HELD_PIECE, shape)
holdQueue.piece.holdEnabled = false holdQueue.piece.holdEnabled = false
holdQueue.draw() holdQueue.draw()
generationPhase(matrix.piece) generationPhase(matrix.piece)
@ -741,7 +759,7 @@ function pause() {
actionsToRepeat = [] actionsToRepeat = []
scheduler.clearInterval(lockPhase) scheduler.clearInterval(lockPhase)
scheduler.clearTimeout(lockDown) scheduler.clearTimeout(lockDown)
scheduler.clearTimeout(autorepeat) scheduler.clearTimeout(AUTOREPEAT_DELAY)
scheduler.clearInterval(clock) scheduler.clearInterval(clock)
holdQueue.draw() holdQueue.draw()
matrix.draw() matrix.draw()
@ -765,7 +783,7 @@ function printTempTexts(text) {
tempTexts.push(text) tempTexts.push(text)
messageDiv.innerHTML = tempTexts[0] messageDiv.innerHTML = tempTexts[0]
if (!scheduler.intervalTasks.has(delTempTexts)) if (!scheduler.intervalTasks.has(delTempTexts))
scheduler.setInterval(delTempTexts, TEMP_TEXTS_DELAY) scheduler.setInterval(delTempTexts, DELAY.MESSAGE)
} }
function delTempTexts(self) { function delTempTexts(self) {
@ -802,8 +820,8 @@ function applySettings() {
actions[STATE.PAUSED][getKeyName("pause")] = resume actions[STATE.PAUSED][getKeyName("pause")] = resume
actions[STATE.GAME_OVER] = {} actions[STATE.GAME_OVER] = {}
autorepeatDelay = localStorage.getItem("autorepeatDelay") || AUTOREPEAT_DELAY autorepeatDelay = localStorage.getItem("autorepeatDelay") || DELAY.AUTOREPEAT_DELAY
autorepeatPeriod = localStorage.getItem("autorepeatPeriod") || AUTOREPEAT_PERIOD autorepeatPeriod = localStorage.getItem("autorepeatPeriod") || DELAY.AUTOREPEAT_PERIOD
theme = localStorage.getItem("theme") || DEFAULT_THEME theme = localStorage.getItem("theme") || DEFAULT_THEME
loadTheme() loadTheme()