all shapes, autorepeat

This commit is contained in:
Adrien MALINGREY 2019-10-25 02:12:47 +02:00
parent 09e32a38ad
commit 8992f4d02b

View File

@ -1,23 +1,23 @@
Array.prototype.add = function(movement) { Array.prototype.add = function(other) {
return this.map((x, i) => x + movement[i]) return this.map((x, i) => x + other[i])
} }
Array.prototype.rotate = function(spin) { Array.prototype.rotate = function(spin) {
return [spin*pos[1], pos[0]] return [spin*pos[1], pos[0]]
} }
Array.prototype.sample = function() {
return this.splice(Math.floor(Math.random()*this.length), 1)[0]
}
const MINO_SIZE = 20 const MINO_SIZE = 20
const LINES = 20 const MATRIX_LINES = 20
const COLLUMNS = 10 const MATRIX_COLLUMNS = 10
const INIT_POSITION = [4, -1] const NEXT_PIECES = 5
const ACTION_KEY = { const INIT_POSITION = [4, 0]
MOVE_LEFT: "ArrowLeft", const LOCK_DELAY = 500
MOVE_RIGHT: "ArrowRight", const FALL_DELAY = 1000
SOFT_DROP: "ArrowDown", const AUTOREPEAT_DELAY = 300
ROTATE_CW: "ArrowUp", const AUTOREPEAT_PERIOD = 10
ROTATE_CCW: "z"
}
const MOVEMENT = { const MOVEMENT = {
LEFT: [-1, 0], LEFT: [-1, 0],
RIGHT: [1, 0], RIGHT: [1, 0],
@ -27,8 +27,27 @@ const SPIN = {
CW: -1, CW: -1,
CCW: 1 CCW: 1
} }
const T_SPIN = {
NULL: "",
MINI: "MINI\nT-SPIN",
T_SPIN: "T-SPIN"
}
const T_SLOT = {
A: 0,
B: 1,
C: 3,
D: 2
}
const SCORES = [
{LINES_CLEAR_NAME: "", NO_T_SPIN: 0, MINI_T_SPIN: 1, T_SPIN: 4},
{LINES_CLEAR_NAME: "SINGLE", NO_T_SPIN: 1, MINI_T_SPIN: 2, T_SPIN: 8},
{LINES_CLEAR_NAME: "DOUBLE", NO_T_SPIN: 3, T_SPIN: 12},
{LINES_CLEAR_NAME: "TRIPLE", NO_T_SPIN: 5, T_SPIN: 16},
{LINES_CLEAR_NAME: "TETRIS", NO_T_SPIN: 8},
]
shapes = []
class Tetromino { class Tetromino {
constructor() { constructor() {
this.pos = INIT_POSITION this.pos = INIT_POSITION
@ -36,20 +55,71 @@ class Tetromino {
this.rotated_last = false this.rotated_last = false
this.rotation_point_5_used = false this.rotation_point_5_used = false
this.hold_enabled = true this.hold_enabled = true
this.SRS = { this.srs = {
"-1": [ 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]], [[0, 0], [-1, 0], [-1, 1], [0, 2], [-1, -2]],
], ],
1: [ 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]], [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],
], ],
} }
if (!shapes.lenght)
shapes = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']
this.shape = shapes.sample()
switch(this.shape) {
case 'I':
this.color = "cyan"
this.minoes_pos = [[-1, 0], [0, 0], [1, 0], [2, 0]]
this.srs = {
CW: [
[[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
[[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
[[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
[[ 0, 1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
],
CCW: [
[[ 0, 1], [-1, 1], [ 2, 1], [-1, -1], [ 2, 2]],
[[-1, 0], [ 1, 0], [-2, 0], [ 1, -1], [-2, 2]],
[[ 0, -1], [ 1, -1], [-2, -1], [ 1, 1], [-2, -2]],
[[ 1, 0], [-1, 0], [ 2, 0], [-1, 1], [ 2, -2]],
],
}
break
case 'J':
this.color = "blue"
this.minoes_pos = [[-1, -1], [-1, 0], [0, 0], [1, 0]]
break
case 'L':
this.color = "orange"
this.minoes_pos = [[-1, 0], [0, 0], [1, 0], [1, -1]]
break
case 'O':
this.color = "yellow"
this.minoes_pos = [[0, 0], [1, 0], [0, -1], [1, -1]]
this.srs = {
CW: [[]],
CCW: [[]]
}
break
case 'S':
this.color = "green"
this.minoes_pos = [[-1, -1], [0, -1], [0, 0], [1, 0]]
break
case 'T':
this.color = "magenta"
this.minoes_pos = [[-1, 0], [0, 0], [1, 0], [0, -1]]
break
case 'Z':
this.color = "red"
this.minoes_pos = [[-1, -1], [0, -1], [0, 0], [1, 0]]
break
}
} }
get abs_minoes_pos() { get abs_minoes_pos() {
@ -62,15 +132,6 @@ class Tetromino {
} }
class T_Tetrimino extends Tetromino {
constructor() {
super()
this.color = "magenta"
this.minoes_pos = [[-1, 0], [0, 0], [1, 0], [0, 1]]
}
}
function draw_mino(context, x, y, color) { function draw_mino(context, x, y, color) {
context.fillStyle = color context.fillStyle = color
context.fillRect(x*MINO_SIZE, y*MINO_SIZE, MINO_SIZE, MINO_SIZE); context.fillRect(x*MINO_SIZE, y*MINO_SIZE, MINO_SIZE, MINO_SIZE);
@ -80,11 +141,11 @@ function draw_mino(context, x, y, color) {
class Matrix { class Matrix {
constructor() { constructor() {
this.cells = Array.from(Array(COLLUMNS), y => Array(LINES)) this.cells = Array.from(Array(MATRIX_COLLUMNS), y => Array(MATRIX_LINES))
} }
cell_is_occupied(x, y) { cell_is_occupied(x, y) {
return 0 <= x && x < COLLUMNS && y < LINES ? this.cells[x][y] : true return 0 <= x && x < MATRIX_COLLUMNS && y < MATRIX_LINES ? this.cells[x][y] : true
} }
space_to_move(piece_pos, minoes_pos) { space_to_move(piece_pos, minoes_pos) {
@ -99,11 +160,11 @@ class Matrix {
// grid // grid
context.strokeStyle = "rgba(128, 128, 128, 128)"; context.strokeStyle = "rgba(128, 128, 128, 128)";
context.beginPath(); context.beginPath();
for (var x = 0; x <= COLLUMNS*MINO_SIZE; x += MINO_SIZE) { for (var x = 0; x <= MATRIX_COLLUMNS*MINO_SIZE; x += MINO_SIZE) {
context.moveTo(x, 0); context.moveTo(x, 0);
context.lineTo(x, matrixCanvas.height); context.lineTo(x, matrixCanvas.height);
} }
for (var y = 0; y <= LINES*MINO_SIZE; y += MINO_SIZE) { for (var y = 0; y <= MATRIX_LINES*MINO_SIZE; y += MINO_SIZE) {
context.moveTo(0, y); context.moveTo(0, y);
context.lineTo(matrixCanvas.width, y); context.lineTo(matrixCanvas.width, y);
} }
@ -116,18 +177,22 @@ function move(movement) {
const test_pos = tetro.pos.add(movement) const test_pos = tetro.pos.add(movement)
if (matrix.space_to_move(test_pos, tetro.minoes_pos)) { if (matrix.space_to_move(test_pos, tetro.minoes_pos)) {
tetro.pos = test_pos tetro.pos = test_pos
return true
}
else {
return false
} }
} }
function rotate(spin) { function rotate(spin) {
const text_minoes_pos = tetro.minoes_pos.map(pos => [spin*pos[1], pos[0]]) const text_minoes_pos = tetro.minoes_pos.map(pos => [spin*pos[1], pos[0]])
rotation_point = 0 rotation_point = 0
for (const movement of tetro.SRS[spin][tetro.orientation]) { for (const movement of tetro.srs[spin==SPIN.CW?"CW":"CCW"][tetro.orientation]) {
const test_pos = [tetro.pos[0]+movement[0], tetro.pos[1]+movement[1]] const test_pos = [tetro.pos[0]+movement[0], tetro.pos[1]+movement[1]]
if (matrix.space_to_move(test_pos, text_minoes_pos)) { if (matrix.space_to_move(test_pos, text_minoes_pos)) {
tetro.pos = test_pos tetro.pos = test_pos
tetro.minoes_pos = text_minoes_pos tetro.minoes_pos = text_minoes_pos
tetro.orientation = (tetro.orientation + spin + 4) % 4 tetro.orientation = (tetro.orientation - spin + 4) % 4
break; break;
} }
rotation_point++ rotation_point++
@ -138,46 +203,120 @@ function fall() {
move(MOVEMENT.DOWN); move(MOVEMENT.DOWN);
} }
function keyDownHandler(e) { function moveLeft() {
switch(e.key){
case ACTION_KEY.MOVE_LEFT:
move(MOVEMENT.LEFT); move(MOVEMENT.LEFT);
break }
case ACTION_KEY.MOVE_RIGHT:
move(MOVEMENT.RIGHT); function moveRight() {
break move(MOVEMENT.RIGHT)
case ACTION_KEY.SOFT_DROP: }
move(MOVEMENT.DOWN);
break function softDrop() {
case ACTION_KEY.ROTATE_CW: move(MOVEMENT.DOWN)
rotate(SPIN.CW); }
break
case ACTION_KEY.ROTATE_CCW: function hardDrop() {
rotate(SPIN.CCW); while(move(MOVEMENT.DOWN)) {
break
}
}
function rotateCW() {
rotate(SPIN.CW)
}
function rotateCCW() {
rotate(SPIN.CCW)
}
actions = {
"ArrowLeft": moveLeft,
"ArrowRight": moveRight,
"ArrowDown": softDrop,
" ": hardDrop,
"ArrowUp": rotateCW,
"z": rotateCCW
}
pressedKeys = new Set()
repeatableActions = [moveLeft, moveRight, softDrop]
actionsToRepeat = []
autorepeatTimeoutID = null
autorepeatIntervalID = null
function autorepeat() {
if (actionsToRepeat.length) {
actionsToRepeat[0]()
if (autorepeatTimeoutID) {
autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID)
autorepeatIntervalID = setInterval(autorepeat, AUTOREPEAT_PERIOD)
}
}
else {
if (autorepeatTimeoutID)
autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID)
if (autorepeatIntervalID)
autorepeatIntervalID = clearInterval(autorepeatIntervalID)
}
}
function keyDownHandler(e) {
if (!pressedKeys.has(e.key)) {
pressedKeys.add(e.key)
if (e.key in actions) {
action = actions[e.key]
action()
if (repeatableActions.includes(action)) {
actionsToRepeat.unshift(action)
if (autorepeatTimeoutID) {
autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID)
}
if (autorepeatIntervalID) {
autorepeatIntervalID = clearInterval(autorepeatIntervalID)
}
if (actionsToRepeat == softDrop)
autorepeatIntervalID = setInterval(autorepeat, FALL_DELAY / 20)
else
autorepeatTimeoutID = setTimeout(autorepeat, AUTOREPEAT_DELAY)
}
}
} }
} }
function keyUpHandler(e) { function keyUpHandler(e) {
pressedKeys.delete(e.key)
if (e.key in actions) {
action = actions[e.key]
if (actionsToRepeat.includes(action)) {
actionsToRepeat.splice(actionsToRepeat.indexOf(action), 1)
if (!actionsToRepeat.length) {
if (autorepeatTimeoutID) {
autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID)
}
if (autorepeatIntervalID) {
autorepeatIntervalID = clearInterval(autorepeatIntervalID)
}
}
}
}
} }
function draw() { function draw() {
matrixContext.clearRect(0, 0, COLLUMNS*MINO_SIZE, LINES*MINO_SIZE); matrixContext.clearRect(0, 0, MATRIX_COLLUMNS*MINO_SIZE, MATRIX_LINES*MINO_SIZE);
matrix.draw(matrixContext) matrix.draw(matrixContext)
tetro.draw(matrixContext) tetro.draw(matrixContext)
requestAnimationFrame(draw) requestAnimationFrame(draw)
} }
window.onload = function() { window.onload = function() {
matrixCanvas = document.getElementById("matrix"); matrixCanvas = document.getElementById("matrix");
matrixContext = matrixCanvas.getContext("2d"); matrixContext = matrixCanvas.getContext("2d");
tetro = new T_Tetrimino()
matrix = new Matrix() matrix = new Matrix()
tetro = new Tetromino()
document.addEventListener("keydown", keyDownHandler, false); document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false); document.addEventListener("keyup", keyUpHandler, false);
setInterval(fall, 1000); setInterval(fall, FALL_DELAY);
requestAnimationFrame(draw) requestAnimationFrame(draw)
} }