Skip to content

Instantly share code, notes, and snippets.

@mozurin
Created November 30, 2020 09:27
Show Gist options
  • Save mozurin/026907619b8e2f0cf1761c4197f84f3c to your computer and use it in GitHub Desktop.
Save mozurin/026907619b8e2f0cf1761c4197f84f3c to your computer and use it in GitHub Desktop.
Simple tetris game for Pixl.js board
/**
* Simple tetris game for Pixl.js board
*/
// rotate right
g.setRotation(1);
// turn on backlight
LED1.write(1);
// given constants
const hwWidth = 64, hwHeight = 128;
const blockSize = 6;
const tickInterval = 800;
const flashDelay = 100;
const gameOverFlashCount = 6;
const gameOverMessage = 'GAME OVER';
const gameOverPadding = 10;
const gameOverDelay = 2000;
// calculated constants
const vMargin = Math.floor((hwWidth - 2) % blockSize / 2);
const hMargin = Math.floor((hwHeight - 2) % blockSize / 2);
const gameMapWidth = Math.floor((hwWidth - 2) / blockSize);
const gameMapHeight = Math.floor((hwHeight - 2) / blockSize);
// game logic class
class Tetris {
constructor ()
{
this._map = Array(gameMapHeight).fill(0).map(
() => Array(gameMapWidth).fill(0)
);
}
_drawFrame()
{
g.drawRect(
vMargin,
hMargin,
vMargin + gameMapWidth * blockSize + 1,
hMargin + gameMapHeight * blockSize + 1
);
}
_drawBlock(gameX, gameY)
{
const blockX = gameX * blockSize + vMargin + 1;
const blockY = gameY * blockSize + hMargin + 1;
g.drawRect(
blockX,
blockY,
blockX + blockSize - 1,
blockY + blockSize - 1
);
g.drawRect(
blockX + 2,
blockY + 2,
blockX + blockSize - 3,
blockY + blockSize - 3
);
}
_drawFilled()
{
this._map.forEach(
(row, gy) => row.forEach(
(dot, gx) => dot && this._drawBlock(gx, gy)
)
);
}
_drawPiece()
{
if (!this._piece) {
return;
}
this._piece.forEach(
(col, px) => col.forEach(
(dot, py) => {
if (dot) {
this._drawBlock(
px + this._pieceX,
py + this._pieceY
);
}
}
)
);
}
newPiece()
{
switch(Math.floor(Math.random() * 7)) {
case 0:
this._piece = [
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
];
break;
case 1:
this._piece = [
[1, 0, 0],
[1, 1, 0],
[1, 0, 0],
];
break;
case 2:
this._piece = [
[1, 0, 0],
[1, 0, 0],
[1, 1, 0],
];
break;
case 3:
this._piece = [
[1, 1, 0],
[1, 0, 0],
[1, 0, 0],
];
break;
case 4:
this._piece = [
[1, 0, 0],
[1, 1, 0],
[0, 1, 0],
];
break;
case 5:
this._piece = [
[0, 1, 0],
[1, 1, 0],
[1, 0, 0],
];
break;
case 6:
this._piece = [
[1, 1],
[1, 1],
];
break;
}
this._pieceY = 0;
this._pieceX = Math.floor((gameMapWidth - this._piece.length) / 2);
}
_testPiece(piece, pieceX, pieceY)
{
let movable = true;
piece.forEach(
(col, px) => col.forEach(
(dot, py) => {
if (!dot) {
return;
}
if (
pieceX + px < 0 || pieceX + px >= gameMapWidth ||
pieceY + py < 0 || pieceY + py >= gameMapHeight ||
this._map[pieceY + py][pieceX + px]
) {
movable = false;
}
}
)
);
return movable;
}
_movePiece(dx, dy)
{
if (
this._testPiece(this._piece, this._pieceX + dx, this._pieceY + dy)
) {
this._pieceX += dx;
this._pieceY += dy;
return true;
}
return false;
}
_fixPiece()
{
let fix = false;
this._piece.forEach(
(col, px) => col.forEach(
(dot, py) => {
if (!dot) {
return;
}
if (
this._pieceY + py >= gameMapHeight - 1 ||
this._map[this._pieceY + py + 1][
this._pieceX + px
]
) {
fix = true;
}
}
)
);
if (fix) {
this._piece.forEach(
(col, px) => col.forEach(
(dot, py) => {
if (!dot) {
return;
}
this._map[this._pieceY + py][
this._pieceX + px
] = 1;
}
)
);
this._piece = undefined;
this._pieceX = this._pieceY = 0;
}
return fix;
}
tickAndFixPiece()
{
if (this._piece && !this._movePiece(0, 1)) {
return this._fixPiece();
}
return false;
}
movePieceLeft()
{
if (!this._piece) {
return false;
}
return this._movePiece(-1, 0);
}
movePieceRight()
{
if (!this._piece) {
return false;
}
return this._movePiece(1, 0);
}
dropPiece()
{
if (!this._piece) {
return false;
}
let offset = 0;
while (
this._testPiece(this._piece, this._pieceX, this._pieceY + offset)
) {
offset++;
}
return this._movePiece(0, offset - 1);
}
rotatePiece()
{
if (!this._piece) {
return false;
}
const newPiece = this._piece.map(
(_, px) => this._piece[0].map(
(_, py) => this._piece[py][this._piece.length - px - 1]
)
);
if (this._testPiece(newPiece, this._pieceX, this._pieceY)) {
this._piece = newPiece;
return true;
}
return false;
}
hasFilledRow()
{
return this._map.some(
(row, gy) => row.every(dot => dot)
);
}
clearFilledRow()
{
return this._map.forEach(
(row, gy) => {
if (row.every(dot => dot)) {
row.fill(0);
}
}
);
}
truncateEmptyRow()
{
const nonEmptyRows = this._map.filter(row => row.some(dot => dot));
this._map = Array(gameMapHeight - nonEmptyRows.length).fill(0).map(
() => Array(gameMapWidth).fill(0)
).concat(nonEmptyRows);
}
isTopRowFilled()
{
return this._map.slice(0, 2).some(
row => row.some(dot => dot)
);
}
draw()
{
g.clear();
this._drawFrame();
this._drawFilled();
this._drawPiece();
g.flip();
}
drawGameOver()
{
const textWidth = g.stringWidth(gameOverMessage);
const boxWidth = textWidth + gameOverPadding * 2;
const boxHeight = g.getFontHeight() + gameOverPadding * 2;
const boxX = Math.floor((hwWidth - boxWidth) / 2);
const boxY = Math.floor((hwHeight - boxHeight) / 2);
g.setColor(0);
g.fillRect(boxX, boxY, boxX + boxWidth - 1, boxY + boxHeight - 1);
g.setColor(1);
g.drawRect(boxX, boxY, boxX + boxWidth - 1, boxY + boxHeight - 1);
g.drawString(
'GAME OVER',
boxX + gameOverPadding,
boxY + gameOverPadding
);
g.flip();
}
}
// main timeline management
let tickTimer;
let game = new Tetris();
game.newPiece();
game.draw();
function tickFunc()
{
if (!game._piece) {
game.newPiece();
} else {
if (game.tickAndFixPiece())
{
if (game.hasFilledRow()) {
game.clearFilledRow();
LED1.write(0);
clearInterval(tickTimer);
game.draw();
setTimeout(
function ()
{
LED1.write(1);
game.truncateEmptyRow();
game.draw();
tickTimer = setInterval(tickFunc, tickInterval);
},
flashDelay
);
return;
} else if (game.isTopRowFilled()) {
// game over
game.draw();
game.drawGameOver();
LED1.write(0);
clearInterval(tickTimer);
let counter = 0;
let ledFlag = true;
const flashTimer = setInterval(
function ()
{
LED1.write(ledFlag);
ledFlag = !ledFlag;
if (++counter >= gameOverFlashCount) {
LED1.write(1);
clearInterval(flashTimer);
setTimeout(
function ()
{
game = new Tetris();
game.newPiece();
game.draw();
tickTimer = setInterval(
tickFunc,
tickInterval
);
},
gameOverDelay
)
}
},
flashDelay
);
return;
}
}
}
game.draw();
}
tickTimer = setInterval(tickFunc, tickInterval);
// button 1 == move left
setWatch(
function()
{
if (game.movePieceLeft()) {
game.draw();
}
},
BTN1,
{repeat: true, debounce: 0}
);
// button 4 == move right
setWatch(
function()
{
if (game.movePieceRight()) {
game.draw();
}
},
BTN4,
{repeat: true, debounce: 0}
);
// button 3 == rotate piece (right only)
setWatch(
function()
{
if (game.rotatePiece()) {
game.draw();
}
},
BTN3,
{repeat: true, debounce: 0}
);
// button 2 == drop piece
setWatch(
function()
{
if (game.dropPiece()) {
game.draw();
}
},
BTN2,
{repeat: true, debounce: 0}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment