score, game over
This commit is contained in:
parent
f7bf91f085
commit
6ef40f608d
|
|
@ -14,14 +14,15 @@ const LOCK_DELAY = 5
|
|||
|
||||
type Shape = { x: number, y: number, shape: string, rotation: number }
|
||||
|
||||
let score = 0
|
||||
let player: Shape
|
||||
let nextShape: Shape
|
||||
let grid: string[][] = []
|
||||
let dead = false
|
||||
let downTick = 0
|
||||
let moveTick = 0
|
||||
let tetrisRows: number[] = []
|
||||
let tetrisTimer = 0
|
||||
let clearedLines: number[] = []
|
||||
let clearedLinesTimer = 0
|
||||
let lockTimer = 0
|
||||
|
||||
const BORDER_COLOR = "#b388ff"
|
||||
|
|
@ -29,6 +30,13 @@ const BG_COLOR = "#222"
|
|||
const LINE_COLOR = "black"
|
||||
const UI_BG_COLOR = "#4c3a87"
|
||||
|
||||
const LINE_SCORES = [
|
||||
100,
|
||||
300,
|
||||
500,
|
||||
800
|
||||
]
|
||||
|
||||
const COLORS: Record<string, string> = {
|
||||
I: "cyan",
|
||||
O: "yellow",
|
||||
|
|
@ -158,12 +166,13 @@ const SHAPES: Record<string, number[][][]> = {
|
|||
}
|
||||
|
||||
export function init() {
|
||||
score = 0
|
||||
dead = false
|
||||
downTick = 0
|
||||
moveTick = 0
|
||||
tetrisTimer = TETRIS_FRAMES
|
||||
lockTimer = 0
|
||||
tetrisRows.length = 0
|
||||
clearedLinesTimer = TETRIS_FRAMES
|
||||
clearedLines.length = 0
|
||||
grid.length = 0
|
||||
|
||||
player = newShape()
|
||||
|
|
@ -171,25 +180,25 @@ export function init() {
|
|||
}
|
||||
|
||||
export function update(_delta: number, input: InputState) {
|
||||
const keys = input.pressed
|
||||
if (dead) return
|
||||
|
||||
if (input.justPressed.has(" ")) {
|
||||
rotateShape()
|
||||
}
|
||||
|
||||
// tetris animation
|
||||
if (tetrisRows.length > 0) {
|
||||
tetrisTimer--
|
||||
if (tetrisTimer <= 0) {
|
||||
removeTetrisRows()
|
||||
tetrisRows = []
|
||||
if (clearedLines.length > 0) {
|
||||
clearedLinesTimer--
|
||||
if (clearedLinesTimer <= 0) {
|
||||
removeClearedLines()
|
||||
clearedLines = []
|
||||
}
|
||||
} else if (anyFullRows()) {
|
||||
tetrisRows = findFullRows()
|
||||
tetrisTimer = TETRIS_FRAMES
|
||||
clearedLines = findFullRows()
|
||||
clearedLinesTimer = TETRIS_FRAMES
|
||||
}
|
||||
|
||||
const hit = blocked()
|
||||
const hit = isBlocked()
|
||||
if (!hit) lockTimer = LOCK_DELAY
|
||||
|
||||
if (hit) {
|
||||
|
|
@ -201,25 +210,8 @@ export function update(_delta: number, input: InputState) {
|
|||
}
|
||||
}
|
||||
|
||||
if (++moveTick % MOVE_TICK === 0) {
|
||||
if (
|
||||
(keys.has("ArrowLeft") || keys.has("a")) &&
|
||||
!collision(player.x - 1, player.y, player.rotation)
|
||||
)
|
||||
player.x = Math.max(0, player.x - 1)
|
||||
|
||||
if (
|
||||
(keys.has("ArrowRight") || keys.has("d")) &&
|
||||
!collision(player.x + 1, player.y, player.rotation)
|
||||
)
|
||||
player.x = Math.min(COLS, player.x + 1)
|
||||
|
||||
if (
|
||||
(keys.has("ArrowDown") || keys.has("s")) &&
|
||||
!collision(player.x, player.y + 1, player.rotation)
|
||||
)
|
||||
player.y += 1
|
||||
}
|
||||
if (++moveTick % MOVE_TICK === 0)
|
||||
detectMovement(input)
|
||||
|
||||
if (++downTick % DOWN_TICK === 0)
|
||||
if (!collision(player.x, player.y + 1, player.rotation)) {
|
||||
|
|
@ -228,7 +220,6 @@ export function update(_delta: number, input: InputState) {
|
|||
}
|
||||
|
||||
export function draw(game: GameContext) {
|
||||
// game.clear("#654321") // brown
|
||||
game.clear(UI_BG_COLOR)
|
||||
|
||||
const boardW = COLS * CELL
|
||||
|
|
@ -244,46 +235,35 @@ export function draw(game: GameContext) {
|
|||
game.rectfill(0, 0, boardW, boardH, LINE_COLOR)
|
||||
|
||||
// draw border
|
||||
for (let col = -1; col <= COLS; col++) {
|
||||
drawBlock(game, col, -1, BORDER_COLOR)
|
||||
drawBlock(game, col, ROWS, BORDER_COLOR)
|
||||
}
|
||||
for (let row = 0; row < ROWS; row++) {
|
||||
drawBlock(game, -1, row, BORDER_COLOR)
|
||||
drawBlock(game, COLS, row, BORDER_COLOR)
|
||||
}
|
||||
drawBorder(game)
|
||||
|
||||
// draw board
|
||||
for (let row = 0; row < ROWS; row++) {
|
||||
for (let col = 0; col < COLS; col++) {
|
||||
let color = grid[row]?.[col] || BG_COLOR
|
||||
if (tetrisRows.includes(row)) color = "white"
|
||||
drawBlock(game, col, row, color)
|
||||
}
|
||||
}
|
||||
drawBoard(game)
|
||||
|
||||
// player shape
|
||||
const shape = SHAPES[player.shape]![player.rotation]!
|
||||
for (const row in shape) {
|
||||
for (const col in shape[row]!) {
|
||||
const filled = shape[row][col]
|
||||
if (!filled) continue
|
||||
|
||||
const x = player.x + Number(col)
|
||||
const y = player.y + Number(row)
|
||||
|
||||
drawBlock(game, x, y, COLORS[player.shape]!)
|
||||
}
|
||||
}
|
||||
drawPlayer(game)
|
||||
|
||||
// "next shape" UI
|
||||
drawPreview(game)
|
||||
|
||||
// high score
|
||||
game.text(`Score: ${score}`, (COLS + 2) * CELL, (8 * CELL) + 10, "yellow", 15)
|
||||
|
||||
c.restore()
|
||||
|
||||
// ya dead
|
||||
if (dead) {
|
||||
game.centerText("GAME OVER", "red", 24)
|
||||
if (dead)
|
||||
game.centerText("GAME OVER", "red", 60)
|
||||
}
|
||||
|
||||
function spawn() {
|
||||
player = nextShape
|
||||
player.x = 3
|
||||
player.y = 0
|
||||
nextShape = newShape()
|
||||
|
||||
if (collision(player.x, player.y, player.rotation)) {
|
||||
dead = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,11 +291,12 @@ function findFullRows(): number[] {
|
|||
return rows
|
||||
}
|
||||
|
||||
function removeTetrisRows() {
|
||||
function removeClearedLines() {
|
||||
const newGrid: string[][] = []
|
||||
score += LINE_SCORES[clearedLines.length - 1] || 0
|
||||
|
||||
for (let y = 0; y < ROWS; y++)
|
||||
if (!tetrisRows.includes(y))
|
||||
if (!clearedLines.includes(y))
|
||||
newGrid.push(grid[y] ?? [])
|
||||
|
||||
// add empty rows on top to restore height
|
||||
|
|
@ -323,7 +304,7 @@ function removeTetrisRows() {
|
|||
newGrid.unshift([])
|
||||
|
||||
grid = newGrid
|
||||
tetrisRows = []
|
||||
clearedLines = []
|
||||
}
|
||||
|
||||
function collision(x: number, y: number, rotation: number): boolean {
|
||||
|
|
@ -360,13 +341,10 @@ function lockShape() {
|
|||
}
|
||||
}
|
||||
|
||||
player = nextShape
|
||||
player.x = 3
|
||||
player.y = 0
|
||||
nextShape = newShape()
|
||||
spawn()
|
||||
}
|
||||
|
||||
function blocked(): boolean {
|
||||
function isBlocked(): boolean {
|
||||
const shape = SHAPES[player.shape]![player.rotation]!
|
||||
let hit = false
|
||||
|
||||
|
|
@ -390,6 +368,28 @@ function blocked(): boolean {
|
|||
return hit
|
||||
}
|
||||
|
||||
function detectMovement(input: InputState) {
|
||||
const keys = input.pressed
|
||||
|
||||
if (
|
||||
(keys.has("ArrowLeft") || keys.has("a")) &&
|
||||
!collision(player.x - 1, player.y, player.rotation)
|
||||
)
|
||||
player.x = Math.max(0, player.x - 1)
|
||||
|
||||
if (
|
||||
(keys.has("ArrowRight") || keys.has("d")) &&
|
||||
!collision(player.x + 1, player.y, player.rotation)
|
||||
)
|
||||
player.x = Math.min(COLS, player.x + 1)
|
||||
|
||||
if (
|
||||
(keys.has("ArrowDown") || keys.has("s")) &&
|
||||
!collision(player.x, player.y + 1, player.rotation)
|
||||
)
|
||||
player.y += 1
|
||||
}
|
||||
|
||||
function drawBlock(game: GameContext, x: number, y: number, color: string) {
|
||||
game.rectfill(
|
||||
x * CELL,
|
||||
|
|
@ -429,20 +429,19 @@ function drawPreview(game: GameContext) {
|
|||
// draw board
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
let color = grid[row]?.[col] || BG_COLOR
|
||||
drawBlock(game, previewX + col, previewY + row, color)
|
||||
drawBlock(game, previewX + col, previewY + row, BG_COLOR)
|
||||
}
|
||||
}
|
||||
|
||||
// draw border
|
||||
for (let col = -1; col < cols; col++) {
|
||||
drawBlock(game, previewX + col + 1, -1, BORDER_COLOR)
|
||||
drawBlock(game, previewX + col + 1, rows, BORDER_COLOR)
|
||||
drawBlock(game, previewX + col + 1, previewY + -1, BORDER_COLOR)
|
||||
drawBlock(game, previewX + col + 1, previewY + rows, BORDER_COLOR)
|
||||
}
|
||||
|
||||
for (let row = -1; row < rows; row++) {
|
||||
drawBlock(game, previewX, row, BORDER_COLOR)
|
||||
drawBlock(game, previewX + cols, row, BORDER_COLOR)
|
||||
drawBlock(game, previewX, previewY + row, BORDER_COLOR)
|
||||
drawBlock(game, previewX + cols, previewY + row, BORDER_COLOR)
|
||||
}
|
||||
|
||||
const next = SHAPES[nextShape.shape]![0]!
|
||||
|
|
@ -476,3 +475,39 @@ function rotateShape() {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
function drawBorder(game: GameContext) {
|
||||
for (let col = -1; col <= COLS; col++) {
|
||||
drawBlock(game, col, -1, BORDER_COLOR)
|
||||
drawBlock(game, col, ROWS, BORDER_COLOR)
|
||||
}
|
||||
for (let row = 0; row < ROWS; row++) {
|
||||
drawBlock(game, -1, row, BORDER_COLOR)
|
||||
drawBlock(game, COLS, row, BORDER_COLOR)
|
||||
}
|
||||
}
|
||||
|
||||
function drawBoard(game: GameContext) {
|
||||
for (let row = 0; row < ROWS; row++) {
|
||||
for (let col = 0; col < COLS; col++) {
|
||||
let color = grid[row]?.[col] || BG_COLOR
|
||||
if (clearedLines.includes(row)) color = "white"
|
||||
drawBlock(game, col, row, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawPlayer(game: GameContext) {
|
||||
const shape = SHAPES[player.shape]![player.rotation]!
|
||||
for (const row in shape) {
|
||||
for (const col in shape[row]!) {
|
||||
const filled = shape[row][col]
|
||||
if (!filled) continue
|
||||
|
||||
const x = player.x + Number(col)
|
||||
const y = player.y + Number(row)
|
||||
|
||||
drawBlock(game, x, y, COLORS[player.shape]!)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user