all shapes, autorepeat
This commit is contained in:
parent
09e32a38ad
commit
8992f4d02b
257
js/webtris.js
257
js/webtris.js
@ -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() {
|
||||||
@ -61,15 +131,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
|
||||||
@ -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 moveLeft() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
function keyDownHandler(e) {
|
||||||
switch(e.key){
|
if (!pressedKeys.has(e.key)) {
|
||||||
case ACTION_KEY.MOVE_LEFT:
|
pressedKeys.add(e.key)
|
||||||
move(MOVEMENT.LEFT);
|
if (e.key in actions) {
|
||||||
break
|
action = actions[e.key]
|
||||||
case ACTION_KEY.MOVE_RIGHT:
|
action()
|
||||||
move(MOVEMENT.RIGHT);
|
if (repeatableActions.includes(action)) {
|
||||||
break
|
actionsToRepeat.unshift(action)
|
||||||
case ACTION_KEY.SOFT_DROP:
|
if (autorepeatTimeoutID) {
|
||||||
move(MOVEMENT.DOWN);
|
autorepeatTimeoutID = clearTimeout(autorepeatTimeoutID)
|
||||||
break
|
}
|
||||||
case ACTION_KEY.ROTATE_CW:
|
if (autorepeatIntervalID) {
|
||||||
rotate(SPIN.CW);
|
autorepeatIntervalID = clearInterval(autorepeatIntervalID)
|
||||||
break
|
}
|
||||||
case ACTION_KEY.ROTATE_CCW:
|
if (actionsToRepeat == softDrop)
|
||||||
rotate(SPIN.CCW);
|
autorepeatIntervalID = setInterval(autorepeat, FALL_DELAY / 20)
|
||||||
break
|
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)
|
||||||
}
|
}
|
Reference in New Issue
Block a user