298 lines
7.0 KiB
Plaintext
298 lines
7.0 KiB
Plaintext
var draw = use('draw2d')
|
|
var input = use('input')
|
|
var config = use('config')
|
|
var color = use('color')
|
|
var random = use('random')
|
|
|
|
prosperon.camera.transform.pos = [0,0]
|
|
|
|
// Board constants
|
|
var COLS = 10, ROWS = 20
|
|
var TILE = 6 // each cell is 6x6
|
|
|
|
// Board storage (2D), each cell is either 0 or a [r,g,b,a] color
|
|
var board = []
|
|
|
|
// Gravity timing
|
|
var baseGravity = 0.8 // seconds between drops at level 0
|
|
var gravityTimer = 0
|
|
|
|
// Current piece & position
|
|
var piece = null
|
|
var pieceX = 0
|
|
var pieceY = 0
|
|
|
|
// Next piece
|
|
var nextPiece = null
|
|
|
|
// Score/lines/level
|
|
var score = 0
|
|
var linesCleared = 0
|
|
var level = 0
|
|
|
|
// Rotation lock to prevent spinning with W
|
|
var rotateHeld = false
|
|
var gameOver = false
|
|
|
|
// Horizontal movement gating
|
|
var hMoveTimer = 0
|
|
var hDelay = 0.2 // delay before repeated moves begin
|
|
var hRepeat = 0.05 // time between repeated moves
|
|
var prevLeft = false
|
|
var prevRight = false
|
|
|
|
// Tetrimino definitions
|
|
var SHAPES = {
|
|
I: { color:[0,1,1,1], blocks:[[0,0],[1,0],[2,0],[3,0]] },
|
|
O: { color:[1,1,0,1], blocks:[[0,0],[1,0],[0,1],[1,1]] },
|
|
T: { color:[1,0,1,1], blocks:[[0,0],[1,0],[2,0],[1,1]] },
|
|
S: { color:[0,1,0,1], blocks:[[1,0],[2,0],[0,1],[1,1]] },
|
|
Z: { color:[1,0,0,1], blocks:[[0,0],[1,0],[1,1],[2,1]] },
|
|
J: { color:[0,0,1,1], blocks:[[0,0],[0,1],[1,1],[2,1]] },
|
|
L: { color:[1,0.5,0,1], blocks:[[2,0],[0,1],[1,1],[2,1]] }
|
|
}
|
|
var shapeKeys = array(SHAPES)
|
|
|
|
// Initialize board with empty (0)
|
|
function initBoard() {
|
|
board = []
|
|
var r = 0;
|
|
var row = null;
|
|
var c = 0;
|
|
for (r=0; r<ROWS; r++) {
|
|
row = []
|
|
for (c=0; c<COLS; c++) push(row, 0)
|
|
push(board, row)
|
|
}
|
|
}
|
|
initBoard()
|
|
|
|
function randomShape() {
|
|
var key = shapeKeys[floor(random.random()*length(shapeKeys))]
|
|
// Make a copy of the shape's blocks
|
|
return {
|
|
type: key,
|
|
color: SHAPES[key].color,
|
|
blocks: array(SHAPES[key].blocks, b => [b[0], b[1]])
|
|
}
|
|
}
|
|
|
|
function spawnPiece() {
|
|
piece = nextPiece || randomShape()
|
|
nextPiece = randomShape()
|
|
pieceX = 3
|
|
pieceY = 0
|
|
// Collision on spawn => game over
|
|
if (collides(pieceX, pieceY, piece.blocks)) gameOver = true
|
|
}
|
|
|
|
function collides(px, py, blocks) {
|
|
var i = 0;
|
|
var x = 0;
|
|
var y = 0;
|
|
for (i=0; i<length(blocks); i++) {
|
|
x = px + blocks[i][0]
|
|
y = py + blocks[i][1]
|
|
if (x<0 || x>=COLS || y<0 || y>=ROWS) return true
|
|
if (y>=0 && board[y][x]) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Lock piece into board
|
|
function lockPiece() {
|
|
var i = 0;
|
|
var x = 0;
|
|
var y = 0;
|
|
for (i=0; i<length(piece.blocks); i++) {
|
|
x = pieceX + piece.blocks[i][0]
|
|
y = pieceY + piece.blocks[i][1]
|
|
if (y>=0) board[y][x] = piece.color
|
|
}
|
|
}
|
|
|
|
// Rotate 90° clockwise
|
|
function rotate(blocks) {
|
|
// (x,y) => (y,-x)
|
|
var i = 0;
|
|
var x = 0;
|
|
var y = 0;
|
|
for (i=0; i<length(blocks); i++) {
|
|
x = blocks[i][0]
|
|
y = blocks[i][1]
|
|
blocks[i][0] = y
|
|
blocks[i][1] = -x
|
|
}
|
|
}
|
|
|
|
function clearLines() {
|
|
var lines = 0
|
|
var r = ROWS-1;
|
|
var newRow = null;
|
|
var c = 0;
|
|
for (r=ROWS-1; r>=0;) {
|
|
if (every(board[r], cell => cell)) {
|
|
lines++
|
|
// remove row
|
|
board = array(array(board, 0, r), array(board, r+1))
|
|
// add empty row on top
|
|
newRow = []
|
|
for (c=0; c<COLS; c++) push(newRow, 0)
|
|
board.unshift(newRow)
|
|
} else {
|
|
r--
|
|
}
|
|
}
|
|
// Score
|
|
if (lines==1) score += 100
|
|
else if (lines==2) score += 300
|
|
else if (lines==3) score += 500
|
|
else if (lines==4) score += 800
|
|
linesCleared += lines
|
|
level = floor(linesCleared/10)
|
|
}
|
|
|
|
function placePiece() {
|
|
lockPiece()
|
|
clearLines()
|
|
spawnPiece()
|
|
}
|
|
|
|
// Hard drop
|
|
function hardDrop() {
|
|
while(!collides(pieceX, pieceY+1, piece.blocks)) pieceY++
|
|
placePiece()
|
|
}
|
|
|
|
spawnPiece()
|
|
|
|
this.update = function(dt) {
|
|
if (gameOver) return
|
|
|
|
// ======= Horizontal Movement Gate =======
|
|
var leftPressed = input.keyboard.down('a')
|
|
var rightPressed = input.keyboard.down('d')
|
|
var horizontalMove = 0
|
|
|
|
// If user just pressed A, move once & start gating
|
|
if (leftPressed && !prevLeft) {
|
|
horizontalMove = -1
|
|
hMoveTimer = hDelay
|
|
}
|
|
// If user is holding A & the timer is up, move again, then reset timer to repeat
|
|
else if (leftPressed && hMoveTimer <= 0) {
|
|
horizontalMove = -1
|
|
hMoveTimer = hRepeat
|
|
}
|
|
|
|
// Same logic for D
|
|
if (rightPressed && !prevRight) {
|
|
horizontalMove = 1
|
|
hMoveTimer = hDelay
|
|
} else if (rightPressed && hMoveTimer <= 0) {
|
|
horizontalMove = 1
|
|
hMoveTimer = hRepeat
|
|
}
|
|
|
|
// Move horizontally if it doesn't collide
|
|
if (horizontalMove < 0 && !collides(pieceX-1, pieceY, piece.blocks)) pieceX--
|
|
else if (horizontalMove > 0 && !collides(pieceX+1, pieceY, piece.blocks)) pieceX++
|
|
|
|
// If neither A nor D is pressed, reset the timer so next press is immediate
|
|
if (!leftPressed && !rightPressed) {
|
|
hMoveTimer = 0
|
|
}
|
|
|
|
// Decrement horizontal timer
|
|
hMoveTimer -= dt
|
|
prevLeft = leftPressed
|
|
prevRight = rightPressed
|
|
// ======= End Horizontal Movement Gate =======
|
|
|
|
// Rotate with W (once per press, no spinning)
|
|
var test = null;
|
|
if (input.keyboard.down('w')) {
|
|
if (!rotateHeld) {
|
|
rotateHeld = true
|
|
test = array(piece.blocks, b => [b[0], b[1]])
|
|
rotate(test)
|
|
if (!collides(pieceX, pieceY, test)) piece.blocks = test
|
|
}
|
|
} else {
|
|
rotateHeld = false
|
|
}
|
|
|
|
// Soft drop if S is held (accelerates gravity)
|
|
var fallSpeed = input.keyboard.down('s') ? 10 : 1
|
|
|
|
// Gravity
|
|
gravityTimer += dt * fallSpeed
|
|
var dropInterval = max(0.1, baseGravity - level*0.05)
|
|
if (gravityTimer >= dropInterval) {
|
|
gravityTimer = 0
|
|
if (!collides(pieceX, pieceY+1, piece.blocks)) {
|
|
pieceY++
|
|
} else {
|
|
placePiece()
|
|
}
|
|
}
|
|
|
|
// Hard drop if space is held
|
|
if (input.keyboard.down('space')) {
|
|
// hardDrop()
|
|
}
|
|
}
|
|
|
|
this.hud = function() {
|
|
// Clear screen
|
|
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
|
|
|
|
// Draw board
|
|
var r = 0;
|
|
var c = 0;
|
|
var cell = null;
|
|
var i = 0;
|
|
var x = 0;
|
|
var y = 0;
|
|
var nx = 0;
|
|
var ny = 0;
|
|
var dx = 0;
|
|
var dy = 0;
|
|
for (r=0; r<ROWS; r++) {
|
|
for (c=0; c<COLS; c++) {
|
|
cell = board[r][c]
|
|
if (!cell) continue
|
|
draw.rectangle({x:c*TILE, y:(ROWS-1-r)*TILE, width:TILE, height:TILE}, cell)
|
|
}
|
|
}
|
|
|
|
// Draw falling piece
|
|
if (!gameOver && piece) {
|
|
for (i=0; i<length(piece.blocks); i++) {
|
|
x = pieceX + piece.blocks[i][0]
|
|
y = pieceY + piece.blocks[i][1]
|
|
draw.rectangle({x:x*TILE, y:(ROWS-1-y)*TILE, width:TILE, height:TILE}, piece.color)
|
|
}
|
|
}
|
|
|
|
// Next piece window
|
|
draw.text("Next", {x:70, y:5, width:50, height:10}, null, 0, color.white)
|
|
if (nextPiece) {
|
|
for (i=0; i<length(nextPiece.blocks); i++) {
|
|
nx = nextPiece.blocks[i][0]
|
|
ny = nextPiece.blocks[i][1]
|
|
dx = 12 + nx
|
|
dy = 16 - ny
|
|
draw.rectangle({x:dx*TILE, y:(ROWS-1-dy)*TILE, width:TILE, height:TILE}, nextPiece.color)
|
|
}
|
|
}
|
|
|
|
// Score & Level
|
|
var info = "Score: " + score + "\nLines: " + linesCleared + "\nLevel: " + level
|
|
draw.text(info, {x:70, y:30, width:90, height:50}, null, 0, color.white)
|
|
|
|
if (gameOver) {
|
|
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, null, 0, color.red)
|
|
}
|
|
}
|