149 lines
3.1 KiB
TypeScript
149 lines
3.1 KiB
TypeScript
/// <reference lib="dom" />
|
|
export const game = true
|
|
|
|
import type { GameContext, InputState } from "@/shared/game"
|
|
|
|
const CELL = 20
|
|
const COLS = 30 // 600 px wide
|
|
const ROWS = 12 // 240 px tall of bricks
|
|
|
|
const PADDLE_W = 6
|
|
const PADDLE_H = 1
|
|
const BALL_SPEED = 4
|
|
|
|
const ROW_COLORS = ["red", "orange", "yellow", "green", "blue", "magenta"]
|
|
|
|
let paddleX = 0
|
|
let ball = { x: 0, y: 0, dx: BALL_SPEED, dy: -BALL_SPEED }
|
|
let bricks: { x: number, y: number, alive: boolean }[] = []
|
|
let score = 0
|
|
let dead = false
|
|
|
|
export function init() {
|
|
paddleX = (COLS * CELL - PADDLE_W * CELL) / 2
|
|
ball = {
|
|
x: COLS * CELL / 2,
|
|
y: ROWS * CELL + 60,
|
|
dx: BALL_SPEED,
|
|
dy: -BALL_SPEED
|
|
}
|
|
|
|
bricks = []
|
|
for (let r = 0; r < ROWS; r++) {
|
|
for (let c = 0; c < COLS; c += 2) {
|
|
bricks.push({
|
|
x: c * CELL,
|
|
y: r * CELL,
|
|
alive: true
|
|
})
|
|
}
|
|
}
|
|
|
|
dead = false
|
|
}
|
|
|
|
export function update(_delta: number, input: InputState) {
|
|
if (dead) return
|
|
|
|
if (input.pressed.has("ArrowLeft") || input.pressed.has("a")) {
|
|
paddleX -= 6
|
|
}
|
|
if (input.pressed.has("ArrowRight") || input.pressed.has("d")) {
|
|
paddleX += 6
|
|
}
|
|
|
|
// keep paddle on screen
|
|
paddleX = Math.max(0, Math.min(paddleX, COLS * CELL - PADDLE_W * CELL))
|
|
|
|
// move ball
|
|
ball.x += ball.dx
|
|
ball.y += ball.dy
|
|
|
|
// wall bounce
|
|
if (ball.x < 0 || ball.x > COLS * CELL) ball.dx *= -1
|
|
if (ball.y < 0) ball.dy *= -1
|
|
|
|
// paddle bounce
|
|
const paddleY = ROWS * CELL + 60
|
|
const paddleH = CELL
|
|
|
|
if (
|
|
ball.x > paddleX &&
|
|
ball.x < paddleX + PADDLE_W * CELL &&
|
|
ball.y + 6 >= paddleY &&
|
|
ball.y - 6 <= paddleY + paddleH
|
|
) {
|
|
ball.dy *= -1
|
|
ball.y = paddleY - 6
|
|
}
|
|
|
|
// brick collision
|
|
for (const b of bricks) {
|
|
if (!b.alive) continue
|
|
if (
|
|
ball.x > b.x &&
|
|
ball.x < b.x + (CELL * 2) &&
|
|
ball.y > b.y &&
|
|
ball.y < b.y + CELL
|
|
) {
|
|
score += 100
|
|
b.alive = false
|
|
ball.dy *= -1
|
|
}
|
|
}
|
|
|
|
// death
|
|
if (ball.y > ROWS * CELL + 100) dead = true
|
|
}
|
|
|
|
export function draw(game: GameContext) {
|
|
game.clear("#6C6FF6")
|
|
|
|
const boardW = COLS * CELL
|
|
const boardH = ROWS * CELL + 100
|
|
const offsetX = (game.width - boardW) / 2
|
|
const offsetY = (game.height - boardH) / 2
|
|
|
|
const c = game.ctx
|
|
c.save()
|
|
c.translate(offsetX, offsetY)
|
|
|
|
// background
|
|
game.rectfill(0, 0, boardW, boardH, "black")
|
|
|
|
// paddle
|
|
game.rectfill(
|
|
paddleX,
|
|
ROWS * CELL + 60,
|
|
paddleX + PADDLE_W * CELL,
|
|
ROWS * CELL + 60 + CELL,
|
|
"white"
|
|
)
|
|
|
|
// ball
|
|
game.circfill(ball.x, ball.y, 6, "red")
|
|
|
|
// bricks
|
|
for (const b of bricks) {
|
|
if (b.alive) {
|
|
const color = pickColor(b.x, b.y)
|
|
game.rectfill(b.x, b.y, (b.x + (CELL * 2)) - .5, (b.y + CELL) - .5, color)
|
|
}
|
|
}
|
|
// score!
|
|
game.text(`Score: ${score}`, 5, boardH - 18, "cyan", 12)
|
|
|
|
c.restore()
|
|
|
|
// ya dead
|
|
if (dead) {
|
|
game.centerTextX("GAME OVER", boardH + 30, "red", 24)
|
|
}
|
|
}
|
|
|
|
function pickColor(x: number, y: number): string {
|
|
const row = Math.floor(y / CELL)
|
|
const colorIndex = Math.floor(row / 2) % ROW_COLORS.length
|
|
return ROW_COLORS[colorIndex] || "white"
|
|
}
|