567 lines
18 KiB
JavaScript
Executable File
567 lines
18 KiB
JavaScript
Executable File
/* About: Tetris for HTML, you can insert it in anny Div tagged with an id
|
|
* This is a simple elegant sample for how to work with a Canvas
|
|
*/
|
|
class Tetris_CTX2D {
|
|
static get DEBUG_PARENT_OBJECT() { return true; };
|
|
static get AUTO_CONTINUE_ON_FOCUS() { return false; };
|
|
static get SHOW_FPS_INTERVAL() { return 1000 / 4; }; // four times per second
|
|
// static get TICK_TIME() { return 1 / 4; };
|
|
|
|
static get STATE_RUNNING() { return 0; };
|
|
static get STATE_ENDED() { return 1; };
|
|
|
|
static get GRID_COLUMS() { return 10; };
|
|
static get GRID_ROWS() { return 20; };
|
|
|
|
static get SHAPES() {
|
|
return [
|
|
[0, 0, 0, 0,
|
|
1, 1, 1, 1],
|
|
[0, 0, 0, 0,
|
|
1, 1, 1, 0,
|
|
1],
|
|
[0, 0, 0, 0,
|
|
1, 1, 1, 0,
|
|
0, 0, 1],
|
|
[0, 0, 0, 0,
|
|
0, 1, 1, 0,
|
|
0, 1, 1],
|
|
[0, 0, 0, 0,
|
|
1, 1, 0, 0,
|
|
0, 1, 1],
|
|
[0, 0, 0, 0,
|
|
0, 1, 1, 0,
|
|
1, 1],
|
|
[0, 0, 0, 0,
|
|
0, 1, 0, 0,
|
|
1, 1, 1]
|
|
];
|
|
};
|
|
static get COLORS() {
|
|
return [
|
|
'#74A5FD', 'orange', 'blue', '#FFD700', 'red', '#22AB22', '#BA55D3'
|
|
];
|
|
};
|
|
|
|
static get KEY_ARROW_UP() { return 'ArrowUp'; };
|
|
static get KEY_ARROW_DOWN() { return 'ArrowDown'; };
|
|
static get KEY_ARROW_LEFT() { return 'ArrowLeft'; };
|
|
static get KEY_ARROW_RIGHT() { return 'ArrowRight'; };
|
|
static get KEY_W() { return 'w'; };
|
|
static get KEY_S() { return 's'; };
|
|
static get KEY_A() { return 'a'; };
|
|
static get KEY_D() { return 'd'; };
|
|
static get KEY_ENTER() { return 'Enter'; };
|
|
static get KEY_SPACEBAR() { return ' '; };
|
|
static get KEY_ESCAPE() { return 'Escape'; };
|
|
|
|
running = false;
|
|
animationRequestId = 0;
|
|
|
|
constructor(divId, width, height, difficulty = 1, showFps = false) {
|
|
this.createCanvas(divId, width, height);
|
|
this.canvasEl.title = "Playing: Tetris";
|
|
this.ctx = this.canvasEl.getContext("2d");
|
|
this.ctx.textAlign = "center";
|
|
this.difficulty = difficulty;
|
|
|
|
this.BLOCK_WIDTH = this.width / Tetris_CTX2D.GRID_COLUMS;
|
|
this.BLOCK_HEIGHT = this.height / Tetris_CTX2D.GRID_ROWS;
|
|
|
|
this.audioCtx = new AudioContext();
|
|
this.sounds = [];
|
|
this.sounds[0] = new Audio('./snd/pop.mp3');
|
|
this.audioCtx.createMediaElementSource(this.sounds[0]).connect(this.audioCtx.destination);
|
|
this.sounds[1] = new Audio('./snd/click-button.mp3');
|
|
this.audioCtx.createMediaElementSource(this.sounds[1]).connect(this.audioCtx.destination);
|
|
this.sounds[2] = new Audio('./snd/short-success.mp3');
|
|
this.audioCtx.createMediaElementSource(this.sounds[2]).connect(this.audioCtx.destination);
|
|
|
|
this.showFps = showFps;
|
|
if (showFps) {
|
|
// add framecounter and fps variables and html
|
|
this.frameCounter = 0;
|
|
this.initTime = performance.now();
|
|
const el = document.createElement('p');
|
|
this.frameLabel = document.createElement('span');
|
|
const floatRight = document.createElement('span');
|
|
floatRight.style = "float: right;";
|
|
this.fpsCounter = 0;
|
|
this.fpsLabel = document.createElement('span');
|
|
el.appendChild(document.createTextNode('fps: '));
|
|
el.appendChild(this.fpsLabel);
|
|
floatRight.appendChild(document.createTextNode('frame: '));
|
|
floatRight.appendChild(this.frameLabel);
|
|
el.appendChild(floatRight);
|
|
this.divEl.appendChild(el);
|
|
}
|
|
|
|
this.newGame();
|
|
this.canvasEl.addEventListener("keydown", (e) => this.onKeyDown(e));
|
|
// this.canvasEl.addEventListener("keyup", (e) => this.onKeyUp(e));
|
|
this.canvasEl.addEventListener("blur", (e) => this.onBlur(e));
|
|
this.canvasEl.addEventListener("focus", (e) => this.onFocus(e));
|
|
|
|
if (Tetris_CTX2D.DEBUG_PARENT_OBJECT) window.game = this;
|
|
if (typeof terminalAddCommand === "function") terminalAddCommand("restartgame", (t) => this.terminalRestartGame(t));
|
|
if (typeof terminalPrintLn === "function") terminalPrintLn("new Tetris_CTX2D: @", divId, ' ', width, 'x', height, ' difficulty=', difficulty, ' showFps=', showFps);
|
|
|
|
this.drawCanvas();
|
|
}
|
|
|
|
terminalRestartGame(term) {
|
|
term.terminalClose();
|
|
this.restartGame();
|
|
// this.canvasEl.focus();
|
|
setTimeout(() => { this.canvasEl.focus(); this.pausePlayGame(); }, 200);
|
|
}
|
|
|
|
createCanvas(divId, width = 0, height = 0, zoom = 1) {
|
|
this.divEl = document.getElementById(divId);
|
|
if (this.divEl === null) throw new Error("elementId not found: " + divId);
|
|
while (this.divEl.firstChild) {
|
|
this.divEl.removeChild(this.divEl.lastChild);
|
|
}
|
|
this.canvasEl = this.divEl.appendChild(document.createElement("canvas"));
|
|
const c = this.canvasEl;
|
|
this.width = width;
|
|
this.height = height;
|
|
if (width > 0 && height > 0) {
|
|
c.style.width = width * zoom + 'px';
|
|
c.style.height = height * zoom + 'px';
|
|
c.width = width;
|
|
c.height = height;
|
|
}
|
|
c.tabIndex = 0; // improtant for keyboard focus!
|
|
// c.style.imageRendering = 'pixelated';
|
|
}
|
|
|
|
// creates a new 4x4 shape in global variable 'current'
|
|
// 4x4 so as to cover the size when the shape is rotated
|
|
newCurrentShape() {
|
|
this.current = this.nextShape;
|
|
this.nextShape = this.newShape();
|
|
this.currentX = Tetris_CTX2D.GRID_COLUMS / 2 - 4 / 2;
|
|
this.currentY = 0;
|
|
}
|
|
|
|
newShape() {
|
|
let shapeId = Math.floor(Math.random() * Tetris_CTX2D.SHAPES.length);
|
|
for (let i = 0; shapeId == this.nextShapeId && i < 3; i++) {
|
|
shapeId = Math.floor(Math.random() * Tetris_CTX2D.SHAPES.length);
|
|
}
|
|
this.nextShapeId = shapeId;
|
|
const reference = Tetris_CTX2D.SHAPES[shapeId];
|
|
|
|
let newShape = [];
|
|
for (let y = 0; y < 4; ++y) {
|
|
newShape[y] = [];
|
|
for (let x = 0; x < 4; ++x) {
|
|
let i = 4 * y + x;
|
|
if (typeof reference[i] != 'undefined' && reference[i]) {
|
|
newShape[y][x] = shapeId + 1;
|
|
}
|
|
else {
|
|
newShape[y][x] = 0;
|
|
}
|
|
}
|
|
}
|
|
return newShape;
|
|
}
|
|
|
|
// clears the board
|
|
initBoard() {
|
|
this.board = [];
|
|
for (let y = 0; y < Tetris_CTX2D.GRID_ROWS; ++y) {
|
|
this.board[y] = [];
|
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
|
this.board[y][x] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// keep the element moving down, creating new shapes and clearing lines
|
|
tick() {
|
|
if (this.isValidMove(0, 1)) {
|
|
++this.currentY;
|
|
} else if (this.currentY == 0) {
|
|
this.endGame();
|
|
} else {
|
|
this.freezeCurrentShapeToBoard();
|
|
this.clearLines();
|
|
this.newCurrentShape();
|
|
}
|
|
}
|
|
|
|
// stop shape at its position and fix it to board
|
|
freezeCurrentShapeToBoard() {
|
|
if (this.currentY == 0) {
|
|
this.lose = true;
|
|
return;
|
|
}
|
|
this.playSound(0);
|
|
for (let y = 0; y < 4; ++y) {
|
|
for (let x = 0; x < 4; ++x) {
|
|
if (this.current[y][x]) {
|
|
this.board[y + this.currentY][x + this.currentX] = this.current[y][x];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns the rotated shape 'current' perpendicularly anticlockwise
|
|
rotateShape(current) {
|
|
let newCurrent = [];
|
|
for (let y = 0; y < 4; ++y) {
|
|
newCurrent[y] = [];
|
|
for (let x = 0; x < 4; ++x) {
|
|
newCurrent[y][x] = current[3 - x][y];
|
|
}
|
|
}
|
|
|
|
return newCurrent;
|
|
}
|
|
|
|
// check if any lines are filled and clear them
|
|
clearLines() {
|
|
let rowsFilled = 0;
|
|
for (let y = Tetris_CTX2D.GRID_ROWS - 1; y >= 0; --y) {
|
|
let rowFilled = true;
|
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
|
if (this.board[y][x] == 0) {
|
|
rowFilled = false;
|
|
break;
|
|
}
|
|
}
|
|
if (rowFilled) {
|
|
rowsFilled++;
|
|
this.lose = false;
|
|
for (let yy = y; yy > 0; --yy) {
|
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
|
this.board[yy][x] = this.board[yy - 1][x];
|
|
}
|
|
}
|
|
++y;
|
|
}
|
|
}
|
|
if (rowsFilled > 0) {
|
|
let points = 10 * rowsFilled * rowsFilled;
|
|
this.score += points;
|
|
this.playSound(2);
|
|
if (typeof terminalPrintLn === "function") {
|
|
if (rowsFilled > 1) terminalPrintLn("Cleared ", rowsFilled, " lines! ", points, " points earned. Score=", this.score);
|
|
else terminalPrintLn("Cleared a line! ", points, " points earned. Score=", this.score);
|
|
}
|
|
}
|
|
}
|
|
|
|
playSound(index) {
|
|
try {
|
|
const snd = this.sounds[index];
|
|
snd.currentTime = 0;
|
|
snd.play();
|
|
} catch (e) {
|
|
console.log(`Failed to play sound '${index}': ${e}}`)
|
|
}
|
|
}
|
|
|
|
// checks if the resulting position of current shape will be feasible
|
|
isValidMove(offsetX = 0, offsetY = 0, newCurrent = this.current) {
|
|
offsetX = this.currentX + offsetX;
|
|
offsetY = this.currentY + offsetY;
|
|
for (let y = 0; y < 4; ++y) {
|
|
for (let x = 0; x < 4; ++x) {
|
|
if (newCurrent[y][x]) {
|
|
if (typeof this.board[y + offsetY] == 'undefined'
|
|
|| typeof this.board[y + offsetY][x + offsetX] == 'undefined'
|
|
|| this.board[y + offsetY][x + offsetX]
|
|
|| x + offsetX < 0
|
|
|| y + offsetY >= Tetris_CTX2D.GRID_ROWS
|
|
|| x + offsetX >= Tetris_CTX2D.GRID_COLUMS) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
newGame() {
|
|
this.gameState = Tetris_CTX2D.STATE_RUNNING;
|
|
this.tickTime = 0;
|
|
this.stepTime = 1/4;
|
|
if(this.difficulty == 0){
|
|
this.stepTime = 1/2;
|
|
}else{
|
|
this.stepTime = this.stepTime/ this.difficulty;
|
|
}
|
|
this.initBoard();
|
|
this.score = 0;
|
|
this.nextShape = this.newShape();
|
|
this.newCurrentShape();
|
|
this.lose = false;
|
|
}
|
|
|
|
restartGame() {
|
|
this.newGame();
|
|
}
|
|
|
|
endGame() {
|
|
if (typeof terminalPrintLn === "function") terminalPrintLn("Ending tetris with " + this.score + " points.");
|
|
// console.log("Ending with Score" + this.score + " points");
|
|
this.running = false;
|
|
this.gameState = Tetris_CTX2D.STATE_ENDED;
|
|
}
|
|
|
|
startRunning(fn) {
|
|
if (this.animationRequestId != 0) cancelAnimationFrame(this.animationRequestId);
|
|
this.running = true;
|
|
this.prevNow = performance.now();
|
|
if (this.showFps) {
|
|
this.initTime = performance.now();
|
|
}
|
|
this.animationRequestId = requestAnimationFrame(fn);
|
|
}
|
|
|
|
close() {
|
|
if (this.animationRequestId != 0) {
|
|
cancelAnimationFrame(this.animationRequestId);
|
|
this.animationRequestId = 0;
|
|
}
|
|
this.running = false;
|
|
}
|
|
|
|
pausePlayGame() {
|
|
if (this.gameState == Tetris_CTX2D.STATE_ENDED) return;
|
|
this.running = !this.running;
|
|
if (this.running) {
|
|
this.startRunning(() => this.updateCanvas());
|
|
}
|
|
}
|
|
|
|
updateCanvas() {
|
|
const now = performance.now();
|
|
const timeDelta = (now - this.prevNow) / 1000; //timeDelta = (milli - milli) / toSeconds
|
|
this.prevNow = now;
|
|
//#state switch
|
|
if (this.gameState == Tetris_CTX2D.STATE_RUNNING) {
|
|
this.tickTime += timeDelta;
|
|
if (this.tickTime >= this.stepTime) {
|
|
this.tickTime -= this.stepTime;
|
|
this.tick();
|
|
}
|
|
}
|
|
this.drawCanvas();
|
|
if (this.running) { // loop
|
|
this.animationRequestId = requestAnimationFrame(() => this.updateCanvas());
|
|
} else { // not looping
|
|
this.animationRequestId = 0;
|
|
}
|
|
}
|
|
|
|
// draws the board and the moving shape
|
|
drawCanvas() {
|
|
if (this.showFps) {
|
|
this.frameCounter++;
|
|
this.fpsCounter++;
|
|
|
|
const now = performance.now();
|
|
const diff = now - this.initTime;
|
|
if (Tetris_CTX2D.SHOW_FPS_INTERVAL < diff || !this.running) {
|
|
this.initTime = now;
|
|
const seconds = diff / 1000;
|
|
const fps = this.fpsCounter / seconds;
|
|
this.fpsCounter = 0;
|
|
if (this.frameLabel) this.frameLabel.innerHTML = this.frameCounter;
|
|
if (this.fpsLabel) {
|
|
this.fpsLabel.innerHTML = Math.round((fps + Number.EPSILON) * 100) / 100;
|
|
if (!this.running) this.fpsLabel.innerHTML += " (not running)";
|
|
}
|
|
}
|
|
}
|
|
// draw game
|
|
const ctx = this.ctx;
|
|
ctx.clearRect(0, 0, this.width, this.height);
|
|
|
|
//# grid
|
|
ctx.strokeStyle = 'lightgrey';
|
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(x * this.BLOCK_WIDTH, 0);
|
|
ctx.lineTo(x * this.BLOCK_WIDTH, Tetris_CTX2D.GRID_ROWS * this.BLOCK_HEIGHT);
|
|
ctx.stroke();
|
|
}
|
|
for (let y = 0; y < Tetris_CTX2D.GRID_ROWS; ++y) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, y * this.BLOCK_HEIGHT);
|
|
ctx.lineTo(Tetris_CTX2D.GRID_COLUMS * this.BLOCK_WIDTH, y * this.BLOCK_HEIGHT);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// board
|
|
ctx.strokeStyle = '#404040';
|
|
for (let x = 0; x < Tetris_CTX2D.GRID_COLUMS; ++x) {
|
|
for (let y = 0; y < Tetris_CTX2D.GRID_ROWS; ++y) {
|
|
if (this.board[y][x]) {
|
|
ctx.fillStyle = Tetris_CTX2D.COLORS[this.board[y][x] - 1];
|
|
ctx.fillRect(1 + this.BLOCK_WIDTH * (x), 1 + this.BLOCK_HEIGHT * (y), this.BLOCK_WIDTH - 1, this.BLOCK_HEIGHT - 1);
|
|
ctx.strokeRect(1 + this.BLOCK_WIDTH * (x), 1 + this.BLOCK_HEIGHT * (y), this.BLOCK_WIDTH - 1, this.BLOCK_HEIGHT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// next shape
|
|
const offX = 8, offY = 0;
|
|
for (let y = 0; y < 4; ++y) {
|
|
for (let x = 0; x < 4; ++x) {
|
|
if (this.nextShape[y][x]) {
|
|
ctx.fillStyle = Tetris_CTX2D.COLORS[this.nextShape[y][x] - 1];
|
|
ctx.fillRect(1 + this.BLOCK_WIDTH * offX + x * this.BLOCK_WIDTH / 2, 1 + this.BLOCK_HEIGHT * offY + y * this.BLOCK_HEIGHT / 2, this.BLOCK_WIDTH / 2 - 1, this.BLOCK_HEIGHT / 2 - 1);
|
|
ctx.strokeRect(1 + this.BLOCK_WIDTH * offX + x * this.BLOCK_WIDTH / 2, 1 + this.BLOCK_HEIGHT * offY + y * this.BLOCK_HEIGHT / 2, this.BLOCK_WIDTH / 2 - 1, this.BLOCK_HEIGHT / 2 - 1);
|
|
}
|
|
}
|
|
}
|
|
// tickTime
|
|
ctx.strokeStyle = '#8080B0';
|
|
const rotation = this.tickTime / this.stepTime * Math.PI / 2;
|
|
for (let i = 0; i < 4; i++) {
|
|
const r = rotation + i * Math.PI / 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(this.BLOCK_WIDTH, this.BLOCK_HEIGHT);
|
|
ctx.lineTo(this.BLOCK_WIDTH + this.BLOCK_WIDTH * Math.cos(r) / 2, this.BLOCK_HEIGHT + this.BLOCK_HEIGHT * Math.sin(r) / 2);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// current shape
|
|
//# shadow
|
|
ctx.save();
|
|
ctx.shadowColor = '#000000bf';
|
|
ctx.shadowBlur = 3;
|
|
ctx.shadowOffsetX = 2;
|
|
ctx.shadowOffsetY = 2;
|
|
for (let y = 0; y < 4; ++y) {
|
|
for (let x = 0; x < 4; ++x) {
|
|
if (this.current[y][x]) {
|
|
ctx.fillStyle = Tetris_CTX2D.COLORS[this.current[y][x] - 1];
|
|
ctx.fillRect(1 + this.BLOCK_WIDTH * (this.currentX + x), 1 + this.BLOCK_HEIGHT * (this.currentY + y), this.BLOCK_WIDTH - 1, this.BLOCK_HEIGHT - 1);
|
|
}
|
|
}
|
|
}
|
|
ctx.restore(); //end shadow
|
|
ctx.strokeStyle = '#00000060';
|
|
for (let y = 0; y < 4; ++y) {
|
|
for (let x = 0; x < 4; ++x) {
|
|
if (this.current[y][x]) {
|
|
ctx.strokeRect(1 + this.BLOCK_WIDTH * (this.currentX + x), 1 + this.BLOCK_HEIGHT * (this.currentY + y), this.BLOCK_WIDTH - 1, this.BLOCK_HEIGHT - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
const FONT_SIZE = this.width > 440 ? 24 : 16;
|
|
ctx.font = (1.5 * FONT_SIZE) + "px serif";
|
|
ctx.fillStyle = '#408040';
|
|
ctx.fillText("score: " + this.score, this.width / 2, FONT_SIZE * 1.5);
|
|
ctx.font = FONT_SIZE + "px serif";
|
|
// unfocused & paused banner
|
|
if (!this.running) {
|
|
const x = this.width / 2;
|
|
const y = this.height / 2;
|
|
const y2 = y - FONT_SIZE / 2;
|
|
ctx.strokeStyle = '#404040';
|
|
const g = ctx.createLinearGradient(0, 0, this.width, 0);
|
|
g.addColorStop(0, '#404040a0');
|
|
g.addColorStop(0.2, '#404040df');
|
|
g.addColorStop(0.8, '#404040df');
|
|
g.addColorStop(1, '#404040a0');
|
|
ctx.fillStyle = g;
|
|
ctx.fillRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
|
|
ctx.strokeRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2);
|
|
ctx.fillStyle = (this.gameState === Tetris_CTX2D.STATE_ENDED) ? 'red' : 'gray';
|
|
ctx.font = 3 * FONT_SIZE + "px serif";
|
|
let text = (this.gameState === Tetris_CTX2D.STATE_ENDED) ? "Game over!" : "Paused";
|
|
ctx.fillText(text, x, y - 2 * FONT_SIZE);
|
|
ctx.strokeText(text, x, y - 2 * FONT_SIZE);
|
|
ctx.fillStyle = this.isFocused ? 'goldenrod' : 'lightgray';
|
|
ctx.font = FONT_SIZE + "px serif";
|
|
text = this.isFocused ? `Press space or enter to ${(this.gameState === Tetris_CTX2D.STATE_ENDED) ? "start next round" : "continue"}.` : "Click here to continue. (unfocused)";
|
|
ctx.strokeText(text, x, y2 + FONT_SIZE);
|
|
ctx.fillText(text, x, y2 + FONT_SIZE);
|
|
}
|
|
}
|
|
|
|
onKeyDown(e) {
|
|
if (e.key == Tetris_CTX2D.KEY_ESCAPE) {
|
|
if (this.running) this.pausePlayGame()
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
if (this.running && this.gameState == Tetris_CTX2D.STATE_RUNNING) {
|
|
if (e.key == Tetris_CTX2D.KEY_ARROW_UP || e.key == Tetris_CTX2D.KEY_W) {
|
|
let rotatedShape = this.rotateShape(this.current);
|
|
if (this.isValidMove(0, 0, rotatedShape)) {
|
|
this.current = rotatedShape;
|
|
this.playSound(1);
|
|
}
|
|
e.preventDefault();
|
|
return false;
|
|
} else if (e.key == Tetris_CTX2D.KEY_ARROW_DOWN || e.key == Tetris_CTX2D.KEY_S) {
|
|
this.tick();
|
|
this.tickTime = 0;
|
|
e.preventDefault();
|
|
return false;
|
|
} else if (e.key == Tetris_CTX2D.KEY_ARROW_LEFT || e.key == Tetris_CTX2D.KEY_A) {
|
|
if (this.isValidMove(-1)) {
|
|
this.currentX--;
|
|
}
|
|
e.preventDefault();
|
|
return false;
|
|
} else if (e.key == Tetris_CTX2D.KEY_ARROW_RIGHT || e.key == Tetris_CTX2D.KEY_D) {
|
|
if (this.isValidMove(1)) {
|
|
this.currentX++;
|
|
}
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
} else if (this.gameState == Tetris_CTX2D.STATE_ENDED) {
|
|
if (e.key == Tetris_CTX2D.KEY_SPACEBAR || e.key == Tetris_CTX2D.KEY_ENTER) {
|
|
this.restartGame();
|
|
this.startRunning(() => this.updateCanvas());
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
}
|
|
if (e.key == Tetris_CTX2D.KEY_SPACEBAR || e.key == Tetris_CTX2D.KEY_ENTER) {
|
|
this.pausePlayGame();
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
onBlur() {
|
|
this.audioCtx.suspend();
|
|
this.isFocused = false;
|
|
this.canvasEl.style.borderColor = null;
|
|
if (this.running) {
|
|
this.pausePlayGame();
|
|
} else {
|
|
this.drawCanvas();
|
|
}
|
|
}
|
|
|
|
onFocus() {
|
|
this.audioCtx.resume();
|
|
this.isFocused = true;
|
|
this.canvasEl.style.borderColor = "red";
|
|
if (!this.running && Tetris_CTX2D.AUTO_CONTINUE_ON_FOCUS) {
|
|
this.pausePlayGame();
|
|
} else {
|
|
this.drawCanvas();
|
|
}
|
|
}
|
|
}
|
|
|
|
function startTetris(divId, width = 300, height = 600, difficulty = 1, showFps = true) {
|
|
return new Tetris_CTX2D(divId, width, height, difficulty, showFps);
|
|
}
|