lots of change again
This commit is contained in:
parent
9d7fa05272
commit
2012c628ea
@ -53,7 +53,7 @@ canvas {
|
|||||||
|
|
||||||
.stats-values {
|
.stats-values {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
min-width: 80px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.matrix {
|
.matrix {
|
||||||
|
469
js/webtris.js
469
js/webtris.js
@ -1,13 +1,20 @@
|
|||||||
Array.prototype.add = function(other) {
|
Array.prototype.add = function(other) {
|
||||||
return this.map((x, i) => x + other[i])
|
return this.map((x, i) => x + other[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Array.prototype.mul = function(k) {
|
||||||
|
return this.map(x => k * x)
|
||||||
|
}
|
||||||
|
|
||||||
Array.prototype.translate = function(vector) {
|
Array.prototype.translate = function(vector) {
|
||||||
return this.map(pos => pos.add(vector))
|
return this.map(pos => pos.add(vector))
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.prototype.rotate = function(spin) {
|
Array.prototype.rotate = function(spin) {
|
||||||
return [-spin*this[1], spin*this[0]]
|
return [-spin*this[1], spin*this[0]]
|
||||||
}
|
}
|
||||||
Array.prototype.sample = function() {
|
|
||||||
|
Array.prototype.pick = function() {
|
||||||
return this.splice(Math.floor(Math.random()*this.length), 1)[0]
|
return this.splice(Math.floor(Math.random()*this.length), 1)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +32,7 @@ const FALLING_PIECE_POSITION = [4, 0]
|
|||||||
const NEXT_PIECES_POSITIONS = Array.from({length: NEXT_PIECES}, (v, k) => [2, k*4+2])
|
const NEXT_PIECES_POSITIONS = Array.from({length: NEXT_PIECES}, (v, k) => [2, k*4+2])
|
||||||
const LOCK_DELAY = 500
|
const LOCK_DELAY = 500
|
||||||
const FALL_DELAY = 1000
|
const FALL_DELAY = 1000
|
||||||
const AUTOREPEAT_DELAY = 300
|
const AUTOREPEAT_DELAY = 250
|
||||||
const AUTOREPEAT_PERIOD = 10
|
const AUTOREPEAT_PERIOD = 10
|
||||||
const MOVEMENT = {
|
const MOVEMENT = {
|
||||||
LEFT: [-1, 0],
|
LEFT: [-1, 0],
|
||||||
@ -37,7 +44,7 @@ const SPIN = {
|
|||||||
CCW: -1
|
CCW: -1
|
||||||
}
|
}
|
||||||
const T_SPIN = {
|
const T_SPIN = {
|
||||||
NULL: "",
|
NONE: "",
|
||||||
MINI: "MINI\nT-SPIN",
|
MINI: "MINI\nT-SPIN",
|
||||||
T_SPIN: "T-SPIN"
|
T_SPIN: "T-SPIN"
|
||||||
}
|
}
|
||||||
@ -48,13 +55,19 @@ const T_SLOT = {
|
|||||||
D: 2
|
D: 2
|
||||||
}
|
}
|
||||||
const SCORES = [
|
const SCORES = [
|
||||||
{LINES_CLEAR_NAME: "", NO_T_SPIN: 0, MINI_T_SPIN: 1, T_SPIN: 4},
|
{linesClearedName: "", "": 0, "MINI\nT-SPIN": 1, "T-SPIN": 4},
|
||||||
{LINES_CLEAR_NAME: "SINGLE", NO_T_SPIN: 1, MINI_T_SPIN: 2, T_SPIN: 8},
|
{linesClearedName: "SINGLE", "": 1, "MINI\nT-SPIN": 2, "T-SPIN": 8},
|
||||||
{LINES_CLEAR_NAME: "DOUBLE", NO_T_SPIN: 3, T_SPIN: 12},
|
{linesClearedName: "DOUBLE", "": 3, "T-SPIN": 12},
|
||||||
{LINES_CLEAR_NAME: "TRIPLE", NO_T_SPIN: 5, T_SPIN: 16},
|
{linesClearedName: "TRIPLE", "": 5, "T-SPIN": 16},
|
||||||
{LINES_CLEAR_NAME: "TETRIS", NO_T_SPIN: 8},
|
{linesClearedName: "TETRIS", "": 8},
|
||||||
]
|
]
|
||||||
const REPEATABLE_ACTIONS = [moveLeft, moveRight, softDrop]
|
const REPEATABLE_ACTIONS = [moveLeft, moveRight, softDrop]
|
||||||
|
const T_SLOT_POS = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
|
||||||
|
const STATE = {
|
||||||
|
PLAYING: "",
|
||||||
|
PAUSE: "PAUSE",
|
||||||
|
GAME_OVER: "GAME\nOVER"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Scheduler {
|
class Scheduler {
|
||||||
@ -63,12 +76,12 @@ class Scheduler {
|
|||||||
this.timeoutTasks = new Map()
|
this.timeoutTasks = new Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(func, delay) {
|
setInterval(func, delay, ...args) {
|
||||||
this.intervalTasks.set(func, window.setInterval(func, delay))
|
this.intervalTasks.set(func, window.setInterval(func, delay, ...args))
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(func, delay) {
|
setTimeout(func, delay, ...args) {
|
||||||
this.timeoutTasks.set(func, window.setTimeout(func, delay))
|
this.timeoutTasks.set(func, window.setTimeout(func, delay, ...args))
|
||||||
}
|
}
|
||||||
|
|
||||||
clearInterval(func) {
|
clearInterval(func) {
|
||||||
@ -95,109 +108,126 @@ class Tetromino {
|
|||||||
this.rotatedLast = false
|
this.rotatedLast = false
|
||||||
this.rotationPoint5Used = false
|
this.rotationPoint5Used = false
|
||||||
this.holdEnabled = true
|
this.holdEnabled = true
|
||||||
this.srs = {
|
this.locked = false
|
||||||
CW: [
|
this.srs = {}
|
||||||
[[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],
|
this.srs[SPIN.CW] = [
|
||||||
[[0, 0], [ 1, 0], [ 1, 1], [0, -2], [ 1, -2]],
|
[[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],
|
||||||
[[0, 0], [ 1, 0], [ 1, -1], [0, 2], [ 1, 2]],
|
[[0, 0], [ 1, 0], [ 1, 1], [0, -2], [ 1, -2]],
|
||||||
[[0, 0], [-1, 0], [-1, 1], [0, 2], [-1, -2]],
|
[[0, 0], [ 1, 0], [ 1, -1], [0, 2], [ 1, 2]],
|
||||||
],
|
[[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],
|
||||||
CCW: [
|
]
|
||||||
[[0, 0], [ 1, 0], [ 1, -1], [0, 2], [ 1, 2]],
|
this.srs[SPIN.CCW] = [
|
||||||
[[0, 0], [ 1, 0], [ 1, 1], [0, -2], [ 1, -2]],
|
[[0, 0], [ 1, 0], [ 1, -1], [0, 2], [ 1, 2]],
|
||||||
[[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],
|
[[0, 0], [ 1, 0], [ 1, 1], [0, -2], [ 1, -2]],
|
||||||
[[0, 0], [-1, 0], [-1, 1], [0, 2], [-1, -2]],
|
[[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],
|
||||||
],
|
[[0, 0], [-1, 0], [-1, 1], [0, 2], [-1, -2]],
|
||||||
}
|
]
|
||||||
if (shape)
|
if (shape)
|
||||||
this.shape = shape
|
this.shape = shape
|
||||||
else {
|
else {
|
||||||
if (!shapes.length)
|
if (!shapes.length)
|
||||||
shapes = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']
|
shapes = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']
|
||||||
this.shape = shapes.sample()
|
this.shape = shapes.pick()
|
||||||
}
|
}
|
||||||
switch(this.shape) {
|
switch(this.shape) {
|
||||||
case 'I':
|
case 'I':
|
||||||
this.color = "rgb(132, 225, 225)"
|
this.color = "cyan"
|
||||||
this.ghostColor = "rgba(40, 164, 164, 0.5)"
|
this.lightColor = "rgb(234, 250, 250)"
|
||||||
|
this.ghostColor = "rgba(234, 250, 250, 0.5)"
|
||||||
this.minoesPos = [[-1, 0], [0, 0], [1, 0], [2, 0]]
|
this.minoesPos = [[-1, 0], [0, 0], [1, 0], [2, 0]]
|
||||||
this.srs = {
|
this.srs[SPIN.CW] = [
|
||||||
CW: [
|
[[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
|
||||||
[[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
|
[[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
|
||||||
[[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
|
[[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
|
||||||
[[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
|
[[ 0, 1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
|
||||||
[[ 0, 1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
|
]
|
||||||
],
|
this.srs[SPIN.CCW] = [
|
||||||
CCW: [
|
[[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
|
||||||
[[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
|
[[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
|
||||||
[[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
|
[[ 0, -1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
|
||||||
[[ 0, -1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
|
[[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
|
||||||
[[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
|
]
|
||||||
],
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
case 'J':
|
case 'J':
|
||||||
this.color = "rgb(102, 163, 255)"
|
this.color = "blue"
|
||||||
this.ghostColor = "rgba(0, 82, 204, 0.5)"
|
this.lightColor = "rgb(230, 240, 255)"
|
||||||
|
this.ghostColor = "rgba(230, 240, 255, 0.5)"
|
||||||
this.minoesPos = [[-1, -1], [-1, 0], [0, 0], [1, 0]]
|
this.minoesPos = [[-1, -1], [-1, 0], [0, 0], [1, 0]]
|
||||||
break
|
break
|
||||||
case 'L':
|
case 'L':
|
||||||
this.color = "rgb(255, 148, 77)"
|
this.color = "orange"
|
||||||
this.ghostColor = "rgba(204, 82, 0, 0.5)"
|
this.lightColor = "rgb(255, 224, 204)"
|
||||||
|
this.ghostColor = "rgba(255, 224, 204, 0.5)"
|
||||||
this.minoesPos = [[-1, 0], [0, 0], [1, 0], [1, -1]]
|
this.minoesPos = [[-1, 0], [0, 0], [1, 0], [1, -1]]
|
||||||
break
|
break
|
||||||
case 'O':
|
case 'O':
|
||||||
this.color = "rgb(255, 255, 102)"
|
this.color = "yellow"
|
||||||
this.ghostColor = "rgba(204, 204, 0, 0.5)"
|
this.lightColor = "rgb(255, 255, 230)"
|
||||||
|
this.ghostColor = "rgba(255, 255, 230, 0.5)"
|
||||||
this.minoesPos = [[0, 0], [1, 0], [0, -1], [1, -1]]
|
this.minoesPos = [[0, 0], [1, 0], [0, -1], [1, -1]]
|
||||||
this.srs = {
|
this.srs[SPIN.CW] = [[]]
|
||||||
CW: [[]],
|
this.srs[SPIN.CCW] = [[]]
|
||||||
CCW: [[]]
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
case 'S':
|
case 'S':
|
||||||
this.color = "rgb(159, 255, 128)"
|
this.color = "green"
|
||||||
this.ghostColor = "rgb(38, 153, 0, 0.5)"
|
this.lightColor = "rgb(236, 255, 230)"
|
||||||
this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]]
|
this.ghostColor = "rgba(236, 255, 230, 0.5)"
|
||||||
|
this.minoesPos = [[-1, 0], [0, 0], [0, -1], [1, -1]]
|
||||||
break
|
break
|
||||||
case 'T':
|
case 'T':
|
||||||
this.color = "rgb(179, 102, 255)"
|
this.color = "magenta"
|
||||||
this.ghostColor = "rgba(102, 0, 204, 0.5)"
|
this.lightColor= "rgb(242, 230, 255)"
|
||||||
|
this.ghostColor = "rgba(242, 230, 255, 0.5)"
|
||||||
this.minoesPos = [[-1, 0], [0, 0], [1, 0], [0, -1]]
|
this.minoesPos = [[-1, 0], [0, 0], [1, 0], [0, -1]]
|
||||||
break
|
break
|
||||||
case 'Z':
|
case 'Z':
|
||||||
this.color = "rgb(255, 51, 51)"
|
this.color = "red"
|
||||||
this.ghostColor = "rgba(204, 0, 0, 0.5)"
|
this.lightColor = "rgb(255, 230, 230)"
|
||||||
|
this.ghostColor = "rgba(255, 230, 230, 0.5)"
|
||||||
this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]]
|
this.minoesPos = [[-1, -1], [0, -1], [0, 0], [1, 0]]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get absMinoesPos() {
|
get minoesAbsPos() {
|
||||||
return this.minoesPos.translate(this.pos)
|
return this.minoesPos.translate(this.pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
draw(context, ghostYOffset=0) {
|
draw(context, ghost_pos=[0, 0]) {
|
||||||
if (ghostYOffset) {
|
const color = this.locked ? this.lightColor : this.color
|
||||||
|
if (ghost_pos[1]) {
|
||||||
context.save()
|
context.save()
|
||||||
context.shadowColor = this.ghostColor
|
context.shadowColor = this.ghostColor
|
||||||
context.shadowOffsetX = 0
|
context.shadowOffsetX = 0
|
||||||
context.shadowOffsetY = ghostYOffset * MINO_SIZE
|
context.shadowOffsetY = (ghost_pos[1]-this.pos[1]) * MINO_SIZE
|
||||||
context.shadowBlur = 3
|
context.shadowBlur = 3
|
||||||
}
|
this.minoesAbsPos.forEach(pos => drawMino(context, pos, color))
|
||||||
this.absMinoesPos.map(pos => draw_mino(context, ...pos, this.color))
|
|
||||||
if (ghostYOffset)
|
|
||||||
context.restore()
|
context.restore()
|
||||||
|
}
|
||||||
|
this.minoesAbsPos.forEach(pos => drawMino(context, pos, this.lightColor, color, ghost_pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function draw_mino(context, x, y, color, ghostYOffset) {
|
function drawMino(context, pos, color1, color2=null, spotlight=[0, 0]) {
|
||||||
context.fillStyle = color
|
if (color2) {
|
||||||
context.fillRect(x*MINO_SIZE, y*MINO_SIZE, MINO_SIZE, MINO_SIZE)
|
var center = pos.add([0.5, 0.5])
|
||||||
|
spotlight = spotlight.add([0.5, 0.5])
|
||||||
|
var glint = spotlight.mul(0.1).add(center.mul(0.9)).mul(MINO_SIZE)
|
||||||
|
const gradient = context.createRadialGradient(
|
||||||
|
...glint, 2, ...glint.add([6, 4]), 2*MINO_SIZE
|
||||||
|
)
|
||||||
|
gradient.addColorStop(0, color1)
|
||||||
|
gradient.addColorStop(1, color2)
|
||||||
|
context.fillStyle = gradient
|
||||||
|
}
|
||||||
|
else
|
||||||
|
context.fillStyle = color1
|
||||||
|
var topLeft = pos.mul(MINO_SIZE)
|
||||||
|
context.fillRect(...topLeft, MINO_SIZE, MINO_SIZE)
|
||||||
context.lineWidth = 0.5
|
context.lineWidth = 0.5
|
||||||
context.strokeStyle = "white"
|
context.strokeStyle = "white"
|
||||||
context.strokeRect(x*MINO_SIZE, y*MINO_SIZE, MINO_SIZE, MINO_SIZE)
|
context.strokeRect(...topLeft, MINO_SIZE, MINO_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -216,16 +246,17 @@ class HoldQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
timeFormat = new Intl.DateTimeFormat("en-US", {
|
timeFormat = new Intl.DateTimeFormat("en-US", {
|
||||||
hour: "numeric", minute: "2-digit", second: "2-digit", hourCycle: "h24", timeZone: "UTC"
|
hour: "numeric", minute: "2-digit", second: "2-digit", hourCycle: "h24", timeZone: "UTC"
|
||||||
}).format
|
}).format
|
||||||
|
|
||||||
|
|
||||||
class Stats {
|
class Stats {
|
||||||
constructor (div, start_level=1) {
|
constructor (div) {
|
||||||
this.div = div
|
this.div = div
|
||||||
this._score = 0
|
this._score = 0
|
||||||
this.highScore = 0
|
this.highScore = 0
|
||||||
this.level = start_level - 1
|
|
||||||
this.goal = 0
|
this.goal = 0
|
||||||
this.linesCleared = 0
|
this.linesCleared = 0
|
||||||
this.startTime = Date.now()
|
this.startTime = Date.now()
|
||||||
@ -244,8 +275,11 @@ class Stats {
|
|||||||
this.highScore = score
|
this.highScore = score
|
||||||
}
|
}
|
||||||
|
|
||||||
new_level() {
|
newLevel(level=null) {
|
||||||
this.level++
|
if (level)
|
||||||
|
this.level = level
|
||||||
|
else
|
||||||
|
this.level++
|
||||||
this.goal += 5 * this.level
|
this.goal += 5 * this.level
|
||||||
if (this.level <= 20)
|
if (this.level <= 20)
|
||||||
this.fallDelay = 1000 * Math.pow(0.8 - ((this.level - 1) * 0.007), this.level - 1)
|
this.fallDelay = 1000 * Math.pow(0.8 - ((this.level - 1) * 0.007), this.level - 1)
|
||||||
@ -253,13 +287,41 @@ class Stats {
|
|||||||
this.lockDelay = 500 * Math.pow(0.9, this.level - 15)
|
this.lockDelay = 500 * Math.pow(0.9, this.level - 15)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locksDown(tSpin, linesCleared) {
|
||||||
|
var pattern_name = []
|
||||||
|
var pattern_score = 0
|
||||||
|
var combo_score = 0
|
||||||
|
|
||||||
|
if (tSpin)
|
||||||
|
pattern_name.push(tSpin)
|
||||||
|
if (linesCleared) {
|
||||||
|
pattern_name.push(SCORES[linesCleared].linesClearedName)
|
||||||
|
this.combo++
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.combo = -1
|
||||||
|
|
||||||
|
if (linesCleared || tSpin) {
|
||||||
|
pattern_score = SCORES[linesCleared][tSpin]
|
||||||
|
this.goal -= pattern_score
|
||||||
|
pattern_score *= 100 * this.level
|
||||||
|
pattern_name = pattern_name.join("\n")
|
||||||
|
}
|
||||||
|
if (this.combo >= 1)
|
||||||
|
combo_score = (linesCleared == 1 ? 20 : 50) * this.combo * this.level
|
||||||
|
|
||||||
|
this.score += pattern_score + combo_score
|
||||||
|
|
||||||
|
//console.log(pattern_name, pattern_score, this.combo, combo_score)
|
||||||
|
}
|
||||||
|
|
||||||
print() {
|
print() {
|
||||||
this.div.innerHTML = this.score
|
this.div.innerHTML = this.score + "<br/>"
|
||||||
this.div.innerHTML += "<br/>" + this.highScore
|
+ this.highScore + "<br/>"
|
||||||
this.div.innerHTML += "<br/>" + this.level
|
+ this.level + "<br/>"
|
||||||
this.div.innerHTML += "<br/>" + this.goal
|
+ this.goal + "<br/>"
|
||||||
this.div.innerHTML += "<br/>" + this.linesCleared
|
+ this.linesCleared + "<br/>"
|
||||||
this.div.innerHTML += "<br/>" + timeFormat(Date.now() - this.startTime)
|
+ timeFormat(Date.now() - this.startTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,26 +329,23 @@ class Stats {
|
|||||||
class Matrix {
|
class Matrix {
|
||||||
constructor(context) {
|
constructor(context) {
|
||||||
this.context = context
|
this.context = context
|
||||||
this.cells = Array.from(Array(MATRIX_COLUMNS), x => Array(MATRIX_ROWS))
|
this.cells = Array.from(Array(MATRIX_ROWS+3), row => Array(MATRIX_COLUMNS))
|
||||||
this.width = MATRIX_COLUMNS*MINO_SIZE
|
this.width = MATRIX_COLUMNS*MINO_SIZE
|
||||||
this.height = MATRIX_ROWS*MINO_SIZE
|
this.height = MATRIX_ROWS*MINO_SIZE
|
||||||
this.piece = null
|
this.piece = null
|
||||||
}
|
}
|
||||||
|
|
||||||
cellIsOccupied(x, y) {
|
cellIsOccupied(x, y) {
|
||||||
return 0 <= x && x < MATRIX_COLUMNS && y < MATRIX_ROWS ? this.cells[x][y] : true
|
return 0 <= x && x < MATRIX_COLUMNS && y < MATRIX_ROWS ? this.cells[y+3][x] : true
|
||||||
}
|
}
|
||||||
|
|
||||||
spaceToMove(absMinoesPos) {
|
spaceToMove(minoesAbsPos) {
|
||||||
for (const pos of absMinoesPos) {
|
return !minoesAbsPos.some(pos => this.cellIsOccupied(...pos))
|
||||||
if (this.cellIsOccupied(...pos))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
this.context.clearRect(0, 0, this.width, this.height)
|
this.context.clearRect(0, 0, this.width, this.height)
|
||||||
|
|
||||||
// grid
|
// grid
|
||||||
this.context.strokeStyle = "rgba(128, 128, 128, 128)"
|
this.context.strokeStyle = "rgba(128, 128, 128, 128)"
|
||||||
this.context.lineWidth = 0.5
|
this.context.lineWidth = 0.5
|
||||||
@ -300,10 +359,19 @@ class Matrix {
|
|||||||
this.context.lineTo(this.width, y);
|
this.context.lineTo(this.width, y);
|
||||||
}
|
}
|
||||||
this.context.stroke()
|
this.context.stroke()
|
||||||
|
|
||||||
|
// ghost position
|
||||||
|
for (var ghost_pos = Array.from(this.piece.pos); this.spaceToMove(this.piece.minoesPos.translate(ghost_pos)); ghost_pos[1]++) {}
|
||||||
|
ghost_pos[1]--
|
||||||
|
|
||||||
|
// locked minoes
|
||||||
|
this.cells.slice(3).forEach((row, y) => row.forEach((colors, x) => {
|
||||||
|
if (colors) drawMino(this.context, [x, y], ...colors, ghost_pos)
|
||||||
|
}))
|
||||||
|
|
||||||
// falling piece
|
// falling piece
|
||||||
if (this.piece)
|
if (this.piece)
|
||||||
for (var ghostYOffset = 0; this.spaceToMove(this.piece.minoesPos.translate([this.piece.pos[0], this.piece.pos[1]+ghostYOffset])); ghostYOffset++) {}
|
this.piece.draw(this.context, ghost_pos)
|
||||||
this.piece.draw(this.context, --ghostYOffset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,15 +386,55 @@ class NextQueue {
|
|||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
this.context.clearRect(0, 0, this.width, this.height)
|
this.context.clearRect(0, 0, this.width, this.height)
|
||||||
this.pieces.map(piece => piece.draw(this.context))
|
this.pieces.forEach(piece => piece.draw(this.context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function move(movement) {
|
function newLevel(startLevel) {
|
||||||
const test_pos = matrix.piece.pos.add(movement)
|
stats.newLevel(startLevel)
|
||||||
if (matrix.spaceToMove(matrix.piece.minoesPos.translate(test_pos))) {
|
generationPhase()
|
||||||
matrix.piece.pos = test_pos
|
}
|
||||||
|
|
||||||
|
function generationPhase(held_piece=null) {
|
||||||
|
if (!held_piece) {
|
||||||
|
matrix.piece = nextQueue.pieces.shift()
|
||||||
|
nextQueue.pieces.push(new Tetromino())
|
||||||
|
nextQueue.pieces.forEach((piece, i) => piece.pos = NEXT_PIECES_POSITIONS[i])
|
||||||
|
}
|
||||||
|
matrix.piece.pos = FALLING_PIECE_POSITION
|
||||||
|
if (matrix.spaceToMove(matrix.piece.minoesPos.translate(matrix.piece.pos)))
|
||||||
|
fallingPhase()
|
||||||
|
else
|
||||||
|
gameOver()
|
||||||
|
}
|
||||||
|
|
||||||
|
function fallingPhase() {
|
||||||
|
scheduler.clearTimeout(lockPhase)
|
||||||
|
scheduler.clearTimeout(locksDown)
|
||||||
|
matrix.piece.locked = false
|
||||||
|
scheduler.setTimeout(lockPhase, stats.fallDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
function lockPhase() {
|
||||||
|
if (!move(MOVEMENT.DOWN))
|
||||||
|
locksDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
function move(movement, lock=true, testMinoesPos=matrix.piece.minoesPos) {
|
||||||
|
const testPos = matrix.piece.pos.add(movement)
|
||||||
|
if (matrix.spaceToMove(testMinoesPos.translate(testPos))) {
|
||||||
|
matrix.piece.pos = testPos
|
||||||
|
matrix.piece.minoesPos = testMinoesPos
|
||||||
|
if (movement != MOVEMENT.DOWN)
|
||||||
|
matrix.piece.rotatedLast = false
|
||||||
|
if (matrix.spaceToMove(matrix.piece.minoesPos.translate(matrix.piece.pos.add(MOVEMENT.DOWN))))
|
||||||
|
fallingPhase()
|
||||||
|
else if (lock) {
|
||||||
|
matrix.piece.locked = true
|
||||||
|
scheduler.clearTimeout(locksDown)
|
||||||
|
scheduler.setTimeout(locksDown, stats.lockDelay)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -336,77 +444,65 @@ function move(movement) {
|
|||||||
|
|
||||||
function rotate(spin) {
|
function rotate(spin) {
|
||||||
const test_minoes_pos = matrix.piece.minoesPos.map(pos => pos.rotate(spin))
|
const test_minoes_pos = matrix.piece.minoesPos.map(pos => pos.rotate(spin))
|
||||||
rotation_point = 0
|
rotationPoint = 1
|
||||||
for (const movement of matrix.piece.srs[spin==SPIN.CW?"CW":"CCW"][matrix.piece.orientation]) {
|
for (const movement of matrix.piece.srs[spin][matrix.piece.orientation]) {
|
||||||
const test_pos = matrix.piece.pos.add(movement)
|
if (move(movement, false, test_minoes_pos)) {
|
||||||
if (matrix.spaceToMove(test_minoes_pos.translate(test_pos))) {
|
|
||||||
matrix.piece.pos = test_pos
|
|
||||||
matrix.piece.minoesPos = test_minoes_pos
|
|
||||||
matrix.piece.orientation = (matrix.piece.orientation + spin + 4) % 4
|
matrix.piece.orientation = (matrix.piece.orientation + spin + 4) % 4
|
||||||
break;
|
matrix.piece.rotatedLast = true
|
||||||
|
if (rotationPoint == 5)
|
||||||
|
matrix.piece.rotationPoint5Used = true
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
rotation_point++
|
rotationPoint++
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function locksDown(){
|
||||||
|
scheduler.clearInterval(move)
|
||||||
|
if (matrix.piece.minoesAbsPos.every(pos => pos.y < 0))
|
||||||
|
game_over()
|
||||||
|
else {
|
||||||
|
matrix.piece.minoesAbsPos.forEach(pos => matrix.cells[pos[1]+3][pos[0]] = [matrix.piece.lightColor, matrix.piece.color])
|
||||||
|
|
||||||
|
// T-Spin detection
|
||||||
|
var tSpin = T_SPIN.NONE
|
||||||
|
const tSlots = T_SLOT_POS.translate(matrix.piece.pos).map(pos => matrix.cellIsOccupied(pos))
|
||||||
|
if (matrix.piece.rotatedLast && matrix.piece.shape == "T") {
|
||||||
|
const a = tSlots[(matrix.piece.orientation+T_SLOT.A)%4],
|
||||||
|
b = tSlots[(matrix.piece.orientation+T_SLOT.B)%4],
|
||||||
|
c = tSlots[(matrix.piece.orientation+T_SLOT.C)%4],
|
||||||
|
d = tSlots[(matrix.piece.orientation+T_SLOT.D)%4]
|
||||||
|
if (a && b && (c || d))
|
||||||
|
tSpin = T_SPIN.T_SPIN
|
||||||
|
else if (c && d && (a || b))
|
||||||
|
tSpin = matrix.piece.rotationPoint5Used ? T_SPIN.T_SPIN : T_SPIN.MINI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete lines
|
||||||
|
var linesCleared = 0
|
||||||
|
matrix.cells.forEach((row, y) => {
|
||||||
|
if (row.filter(mino => mino.length).length == MATRIX_COLUMNS) {
|
||||||
|
matrix.cells.splice(y, 1)
|
||||||
|
matrix.cells.unshift(Array(MATRIX_COLUMNS))
|
||||||
|
linesCleared++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
stats.locksDown(tSpin, linesCleared)
|
||||||
|
|
||||||
|
if (stats.goal <= 0)
|
||||||
|
newLevel()
|
||||||
|
else
|
||||||
|
generationPhase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fall() {
|
function gameOver() {
|
||||||
move(MOVEMENT.DOWN);
|
state = STATE.GAME_OVER
|
||||||
}
|
scheduler.clearTimeout(lockPhase)
|
||||||
|
scheduler.clearTimeout(locksDown)
|
||||||
function moveLeft() {
|
console.log("GAME OVER")
|
||||||
move(MOVEMENT.LEFT);
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveRight() {
|
|
||||||
move(MOVEMENT.RIGHT)
|
|
||||||
}
|
|
||||||
|
|
||||||
function softDrop() {
|
|
||||||
move(MOVEMENT.DOWN)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hardDrop() {
|
|
||||||
while(move(MOVEMENT.DOWN)) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rotateCW() {
|
|
||||||
rotate(SPIN.CW)
|
|
||||||
}
|
|
||||||
|
|
||||||
function rotateCCW() {
|
|
||||||
rotate(SPIN.CCW)
|
|
||||||
}
|
|
||||||
|
|
||||||
function hold() {
|
|
||||||
if (this.matrix.piece.holdEnabled) {
|
|
||||||
this.matrix.piece.holdEnabled = false
|
|
||||||
clearInterval(lockPhaseIntervalID)
|
|
||||||
var shape = this.matrix.piece.shape
|
|
||||||
this.matrix.piece = this.holdQueue.piece
|
|
||||||
this.holdQueue.piece = new Tetromino(HELD_PIECE_POSITION, shape)
|
|
||||||
this.generationPhase(this.matrix.piece)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function new_level() {
|
|
||||||
stats.new_level()
|
|
||||||
fallIntervalID = setInterval(fall, stats.fallDelay)
|
|
||||||
generationPhase()
|
|
||||||
}
|
|
||||||
|
|
||||||
function generationPhase(held_piece=null) {
|
|
||||||
if (!held_piece) {
|
|
||||||
this.matrix.piece = this.nextQueue.pieces.shift()
|
|
||||||
this.nextQueue.pieces.push(new Tetromino())
|
|
||||||
this.nextQueue.pieces.map((piece, i, pieces) => piece.pos = NEXT_PIECES_POSITIONS[i])
|
|
||||||
}
|
|
||||||
this.matrix.piece.pos = FALLING_PIECE_POSITION
|
|
||||||
/*if (this.matrix.spaceToMove(this.matrix.piece.minoesPos.translate(this.matrix.piece.pos)))
|
|
||||||
fallingPhase()
|
|
||||||
else
|
|
||||||
gameOver()*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function autorepeat() {
|
function autorepeat() {
|
||||||
@ -456,12 +552,56 @@ function keyUpHandler(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveLeft() {
|
||||||
|
move(MOVEMENT.LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveRight() {
|
||||||
|
move(MOVEMENT.RIGHT)
|
||||||
|
}
|
||||||
|
|
||||||
|
function softDrop() {
|
||||||
|
if (move(MOVEMENT.DOWN))
|
||||||
|
stats.score++
|
||||||
|
}
|
||||||
|
|
||||||
|
function hardDrop() {
|
||||||
|
scheduler.clearTimeout(lockPhase)
|
||||||
|
scheduler.clearTimeout(locksDown)
|
||||||
|
while(move(MOVEMENT.DOWN, false)) {
|
||||||
|
stats.score += 2
|
||||||
|
}
|
||||||
|
locksDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateCW() {
|
||||||
|
rotate(SPIN.CW)
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateCCW() {
|
||||||
|
rotate(SPIN.CCW)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hold() {
|
||||||
|
if (this.matrix.piece.holdEnabled) {
|
||||||
|
this.matrix.piece.holdEnabled = false
|
||||||
|
scheduler.clearInterval(move)
|
||||||
|
scheduler.clearInterval(locksDown)
|
||||||
|
var shape = this.matrix.piece.shape
|
||||||
|
this.matrix.piece = this.holdQueue.piece
|
||||||
|
this.holdQueue.piece = new Tetromino(HELD_PIECE_POSITION, shape)
|
||||||
|
this.generationPhase(this.matrix.piece)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
holdQueue.draw()
|
holdQueue.draw()
|
||||||
stats.print()
|
stats.print()
|
||||||
matrix.draw()
|
matrix.draw()
|
||||||
nextQueue.draw()
|
nextQueue.draw()
|
||||||
requestAnimationFrame(draw)
|
|
||||||
|
if (state != STATE.GAME_OVER)
|
||||||
|
requestAnimationFrame(draw)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
@ -486,5 +626,6 @@ window.onload = function() {
|
|||||||
addEventListener("keyup", keyUpHandler, false)
|
addEventListener("keyup", keyUpHandler, false)
|
||||||
requestAnimationFrame(draw)
|
requestAnimationFrame(draw)
|
||||||
|
|
||||||
this.new_level()
|
state = STATE.PLAYING
|
||||||
|
this.newLevel(1)
|
||||||
}
|
}
|
Reference in New Issue
Block a user