mirror of https://github.com/espruino/BangleApps
310 lines
5.8 KiB
JavaScript
310 lines
5.8 KiB
JavaScript
Bangle.setLCDMode("doublebuffered");
|
|
|
|
const storage = require("Storage");
|
|
|
|
var BTN_L = BTN1;
|
|
var BTN_R = BTN3;
|
|
var BTN_ROT = BTN2;
|
|
var BTN_DOWN = BTN5;
|
|
var BTN_PAUSE = BTN4;
|
|
|
|
const W = g.getWidth();
|
|
const H = g.getHeight();
|
|
const CX = W / 2;
|
|
const CY = H / 2;
|
|
|
|
const HEIGHT_BUFFER = 4;
|
|
|
|
const LINES = 20;
|
|
const COLUMNS = 11;
|
|
const CELL_SIZE = Math.floor((H - HEIGHT_BUFFER) / (LINES + 1));
|
|
|
|
const BOARD_X = Math.floor((W - CELL_SIZE * COLUMNS) / 2) + 2;
|
|
const BOARD_Y = Math.floor((H - CELL_SIZE * (LINES + 1)) / 2);
|
|
const BOARD_W = COLUMNS * CELL_SIZE;
|
|
const BOARD_H = LINES * CELL_SIZE;
|
|
|
|
const TEXT_X = BOARD_X + BOARD_W + 10;
|
|
|
|
const BLOCKS = [
|
|
[
|
|
[2, 7],
|
|
[2, 6, 2],
|
|
[0, 7, 2],
|
|
[2, 3, 2]
|
|
],
|
|
[
|
|
[1, 3, 2],
|
|
[6, 3]
|
|
],
|
|
[
|
|
[2, 3, 1],
|
|
[3, 6]
|
|
],
|
|
[
|
|
[2, 2, 6],
|
|
[0, 7, 1],
|
|
[3, 2, 2],
|
|
[4, 7]
|
|
],
|
|
[
|
|
[2, 2, 3],
|
|
[1, 7],
|
|
[6, 2, 2],
|
|
[0, 7, 4]
|
|
],
|
|
[
|
|
[2, 2, 2, 2],
|
|
[0, 15]
|
|
],
|
|
[[3, 3]]
|
|
];
|
|
|
|
const COLOR_WHITE = 0b1111111111111111;
|
|
const COLOR_BLACK = 0b0000000000000000;
|
|
|
|
const BLOCK_COLORS = [
|
|
//0brrrrrggggggbbbbb
|
|
0b0111100000001111,
|
|
0b0000011111100000,
|
|
0b1111100000000011,
|
|
0b0111100111100000,
|
|
0b0000000000011111,
|
|
0b0000001111111111,
|
|
0b1111111111100000
|
|
];
|
|
|
|
const EMPTY_LINE = 0b00000000000000;
|
|
const BOUNDARY = 0b10000000000010;
|
|
const FULL_LINE = 0b01111111111100;
|
|
|
|
let gameOver = false;
|
|
let paused = false;
|
|
let currentBlock = 0;
|
|
let nextBlock = 0;
|
|
let x, y;
|
|
let points;
|
|
let level;
|
|
let lines;
|
|
let board;
|
|
let rotation = 0;
|
|
let ticker = null;
|
|
let needDraw = true;
|
|
let highScore = parseInt(storage.read(".trishig") || 0, 10);
|
|
|
|
function getBlock(a, c, d) {
|
|
const block = BLOCKS[a % 7];
|
|
return block[(a + c) % block.length];
|
|
}
|
|
|
|
function drawBlock(block, screenX, screenY, x, y) {
|
|
for (let row in block) {
|
|
let mask = block[row];
|
|
for (let col = 0; mask; mask >>= 1, col++) {
|
|
if (mask % 2) {
|
|
const dx = screenX + (x + col) * CELL_SIZE;
|
|
const dy = screenY + (y + row) * CELL_SIZE;
|
|
g.fillRect(dx, dy, dx + CELL_SIZE - 3, dy + CELL_SIZE - 3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function drawBoard() {
|
|
g.setColor(COLOR_WHITE);
|
|
g.drawRect(BOARD_X - 3, BOARD_Y - 3, BOARD_X + BOARD_W, BOARD_Y + BOARD_H);
|
|
drawBlock(board, BOARD_X, BOARD_Y, -2, 0);
|
|
|
|
g.setColor(BLOCK_COLORS[currentBlock]);
|
|
drawBlock(getBlock(currentBlock, rotation), BOARD_X, BOARD_Y, x - 2, y);
|
|
}
|
|
|
|
function drawNextBlock() {
|
|
g.setFontAlign(0, -1, 0);
|
|
g.setColor(COLOR_WHITE);
|
|
g.drawString("NEXT BLOCK", BOARD_X / 2, 10);
|
|
g.setColor(BLOCK_COLORS[nextBlock]);
|
|
drawBlock(getBlock(nextBlock, 0), BOARD_X / 2 - 2 * CELL_SIZE, 25, 0, 0);
|
|
}
|
|
|
|
function drawTextLine(text, line) {
|
|
g.drawString(text, TEXT_X, 10 + line * 15);
|
|
}
|
|
|
|
function drawGameState() {
|
|
g.setFontAlign(-1, -1, 0);
|
|
g.setColor(COLOR_WHITE);
|
|
let ln = 0;
|
|
drawTextLine("CLOCK-TRIS", ln++);
|
|
ln++;
|
|
drawTextLine("LVL " + level, ln++);
|
|
drawTextLine("LNS " + lines, ln++);
|
|
drawTextLine("PTS " + points, ln++);
|
|
drawTextLine("TOP " + highScore, ln++);
|
|
}
|
|
|
|
function drawBanner(text) {
|
|
g.setFontAlign(0, 0, 0);
|
|
g.setColor(COLOR_BLACK);
|
|
g.fillRect(CX - 46, CY - 11, CX + 46, CY + 9);
|
|
g.setColor(COLOR_WHITE);
|
|
g.drawRect(CX - 45, CY - 10, CX + 45, CY + 8);
|
|
g.drawString(text, CX, CY);
|
|
}
|
|
|
|
function drawPaused() {
|
|
drawBanner("PAUSED");
|
|
}
|
|
|
|
function drawGameOver() {
|
|
drawBanner("GAME OVER");
|
|
}
|
|
|
|
function draw() {
|
|
g.clear();
|
|
g.setFont("6x8");
|
|
drawBoard();
|
|
drawNextBlock();
|
|
drawGameState();
|
|
if (paused) {
|
|
drawPaused();
|
|
}
|
|
if (gameOver) {
|
|
drawGameOver();
|
|
}
|
|
g.flip();
|
|
}
|
|
|
|
function getNextBlock() {
|
|
currentBlock = nextBlock;
|
|
nextBlock = (Math.random() * BLOCKS.length) | 0;
|
|
x = 6;
|
|
y = 0;
|
|
rotation = 0;
|
|
}
|
|
|
|
function landBlock(a) {
|
|
const block = getBlock(currentBlock, rotation);
|
|
for (let row in block) {
|
|
board[y + (row | 0)] |= block[row] << x;
|
|
}
|
|
|
|
let clearedLines = 0;
|
|
let keepLine = LINES;
|
|
for (let line = LINES - 1; line >= 0; line--) {
|
|
if (board[line] === FULL_LINE) {
|
|
clearedLines++;
|
|
} else {
|
|
board[--keepLine] = board[line];
|
|
}
|
|
}
|
|
|
|
lines += clearedLines;
|
|
if (lines > level * 10) {
|
|
level++;
|
|
setSpeed();
|
|
}
|
|
|
|
while (--keepLine > 0) {
|
|
board[keepLine] = EMPTY_LINE;
|
|
}
|
|
if (clearedLines) {
|
|
points += 100 * (1 << (clearedLines - 1));
|
|
needDraw = true;
|
|
}
|
|
|
|
getNextBlock();
|
|
if (!checkMove(0, 0, 0)) {
|
|
gameOver = true;
|
|
needDraw = true;
|
|
highScore = Math.max(points, highScore);
|
|
storage.write(".trishig", highScore.toString());
|
|
}
|
|
}
|
|
|
|
function checkMove(dx, dy, rot) {
|
|
if (gameOver) {
|
|
startGame();
|
|
return;
|
|
}
|
|
if (paused) {
|
|
return;
|
|
}
|
|
const block = getBlock(currentBlock, rotation + rot);
|
|
for (const row in block) {
|
|
const movedBlockRow = block[row] << (x + dx);
|
|
if (
|
|
row + y === LINES - 1 ||
|
|
movedBlockRow & board[y + dy + row] ||
|
|
movedBlockRow & BOUNDARY
|
|
) {
|
|
if (dy) {
|
|
landBlock();
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
rotation += rot;
|
|
x += dx;
|
|
y += dy;
|
|
needDraw = true;
|
|
return true;
|
|
}
|
|
|
|
function drawLoop() {
|
|
if (needDraw) {
|
|
needDraw = false;
|
|
draw();
|
|
}
|
|
setTimeout(drawLoop, 10);
|
|
}
|
|
|
|
function gameTick() {
|
|
if (!gameOver) {
|
|
checkMove(0, 1, 0);
|
|
}
|
|
}
|
|
|
|
function setSpeed() {
|
|
if (ticker) {
|
|
clearInterval(ticker);
|
|
}
|
|
ticker = setInterval(gameTick, 1000 - level * 100);
|
|
}
|
|
|
|
function togglePause() {
|
|
if (!gameOver) {
|
|
paused = !paused;
|
|
needDraw = true;
|
|
}
|
|
}
|
|
|
|
function startGame() {
|
|
board = [];
|
|
for (let i = 0; i < LINES; i++) {
|
|
board[i] = EMPTY_LINE;
|
|
}
|
|
|
|
gameOver = false;
|
|
points = 0;
|
|
lines = 0;
|
|
level = 0;
|
|
getNextBlock();
|
|
setSpeed();
|
|
needDraw = true;
|
|
}
|
|
|
|
function bindButton(btn, dx, dy, r) {
|
|
setWatch(checkMove.bind(null, dx, dy, r), btn, { repeat: true });
|
|
}
|
|
|
|
bindButton(BTN_L, -1, 0, 0);
|
|
bindButton(BTN_R, 1, 0, 0);
|
|
bindButton(BTN_ROT, 0, 0, 1);
|
|
bindButton(BTN_DOWN, 0, 1, 0);
|
|
|
|
setWatch(togglePause, BTN_PAUSE, { repeat: true });
|
|
|
|
startGame();
|
|
drawLoop();
|