Files
prosperon/examples/tetris/main.ce
2026-02-17 09:15:15 -06:00

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)
}
}