Files
prosperon/examples/tetris/main.ce
2026-01-18 11:22:52 -06:00

273 lines
6.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = []
for (var r=0; r<ROWS; r++) {
var row = []
for (var c=0; c<COLS; c++) row.push(0)
board.push(row)
}
}
initBoard()
function randomShape() {
var key = shapeKeys[floor(random.random()*length(shapeKeys))]
// Make a copy of the shapes 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) {
for (var i=0; i<length(blocks); i++) {
var x = px + blocks[i][0]
var 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() {
for (var i=0; i<length(piece.blocks); i++) {
var x = pieceX + piece.blocks[i][0]
var y = pieceY + piece.blocks[i][1]
if (y>=0) board[y][x] = piece.color
}
}
// Rotate 90° clockwise
function rotate(blocks) {
// (x,y) => (y,-x)
for (var i=0; i<length(blocks); i++) {
var x = blocks[i][0]
var y = blocks[i][1]
blocks[i][0] = y
blocks[i][1] = -x
}
}
function clearLines() {
var lines = 0
for (var r=ROWS-1; r>=0;) {
if (every(board[r], cell => cell)) {
lines++
// remove row
board.splice(r,1)
// add empty row on top
var newRow = []
for (var c=0; c<COLS; c++) newRow.push(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)
if (input.keyboard.down('w')) {
if (!rotateHeld) {
rotateHeld = true
var 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
for (var r=0; r<ROWS; r++) {
for (var c=0; c<COLS; c++) {
var 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 (var i=0; i<length(piece.blocks); i++) {
var x = pieceX + piece.blocks[i][0]
var 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 (var i=0; i<length(nextPiece.blocks); i++) {
var nx = nextPiece.blocks[i][0]
var ny = nextPiece.blocks[i][1]
var dx = 12 + nx
var 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)
}
}