add pong, snake, and tetris examples

This commit is contained in:
2025-02-04 07:38:43 -06:00
parent 5e7c946d43
commit b41f00458b
6 changed files with 488 additions and 0 deletions

5
examples/pong/config.js Normal file
View File

@@ -0,0 +1,5 @@
return {
title: "Pong",
width: 858,
height: 525
}

85
examples/pong/main.js Normal file
View File

@@ -0,0 +1,85 @@
// main.js
var draw = use('draw2d')
var input = use('controller')
var config = use('config')
prosperon.camera.transform.pos = [0,0]
var paddleW = 10, paddleH = 80
var p1 = {x: 30, y: config.height*0.5, speed: 300}
var p2 = {x: config.width-30, y: config.height*0.5, speed: 300}
var ball = {x: 0, y: 0, vx: 220, vy: 150, size: 10}
var score1 = 0, score2 = 0
function resetBall() {
ball.x = config.width*0.5
ball.y = config.height*0.5
// give it a random vertical bounce
ball.vy = (Math.random()<0.5 ? -1:1)*150
// keep horizontal speed to the same magnitude
ball.vx = ball.vx>0 ? 220 : -220
}
resetBall()
this.update = function(dt) {
// Move paddles: positive Y is up, so W/↑ means p.y += speed
if (input.keyboard.down('w')) p1.y += p1.speed*dt
if (input.keyboard.down('s')) p1.y -= p1.speed*dt
// Paddle 2 movement (ArrowUp = up, ArrowDown = down)
if (input.keyboard.down('i')) p2.y += p2.speed*dt
if (input.keyboard.down('k')) p2.y -= p2.speed*dt
// Clamp paddles to screen
if (p1.y < paddleH*0.5) p1.y = paddleH*0.5
if (p1.y > config.height - paddleH*0.5) p1.y = config.height - paddleH*0.5
if (p2.y < paddleH*0.5) p2.y = paddleH*0.5
if (p2.y > config.height - paddleH*0.5) p2.y = config.height - paddleH*0.5
// Move ball
ball.x += ball.vx*dt
ball.y += ball.vy*dt
// Bounce top/bottom
if (ball.y+ball.size*0.5>config.height || ball.y-ball.size*0.5<0) ball.vy = -ball.vy
// Check paddle collisions
// p1 bounding box
var left1 = p1.x - paddleW*0.5, right1 = p1.x + paddleW*0.5
var top1 = p1.y + paddleH*0.5, bottom1 = p1.y - paddleH*0.5
// p2 bounding box
var left2 = p2.x - paddleW*0.5, right2 = p2.x + paddleW*0.5
var top2 = p2.y + paddleH*0.5, bottom2 = p2.y - paddleH*0.5
// ball half-edges
var l = ball.x - ball.size*0.5, r = ball.x + ball.size*0.5
var b = ball.y - ball.size*0.5, t = ball.y + ball.size*0.5
// Collide with paddle 1?
if (r>left1 && l<right1 && t>bottom1 && b<top1)
ball.vx = Math.abs(ball.vx)
// Collide with paddle 2?
if (r>left2 && l<right2 && t>bottom2 && b<top2)
ball.vx = -Math.abs(ball.vx)
// Check left/right out-of-bounds
if (r<0) { score2++; resetBall() }
if (l>config.width) { score1++; resetBall() }
}
this.hud = function() {
// Clear screen black
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
// Draw paddles
draw.rectangle({x:p1.x - paddleW*0.5, y:p1.y - paddleH*0.5, width:paddleW, height:paddleH}, Color.white)
draw.rectangle({x:p2.x - paddleW*0.5, y:p2.y - paddleH*0.5, width:paddleW, height:paddleH}, Color.white)
// Draw ball
draw.rectangle({x:ball.x - ball.size*0.5, y:ball.y - ball.size*0.5, width:ball.size, height:ball.size}, Color.white)
// Simple score display
var msg = score1 + " " + score2
draw.text(msg, {x:0, y:10, width:config.width, height:40}, undefined, 0, Color.white, 0)
}

5
examples/snake/config.js Normal file
View File

@@ -0,0 +1,5 @@
return {
title: "Snake",
width: 600,
height: 600
}

118
examples/snake/main.js Normal file
View File

@@ -0,0 +1,118 @@
// main.js
var draw = use('draw2d')
var render = use('render')
var graphics = use('graphics')
var input = use('input')
var config = use('config')
prosperon.camera.transform.pos = [0,0]
var cellSize = 20
var gridW = Math.floor(config.width / cellSize)
var gridH = Math.floor(config.height / cellSize)
var snake, direction, nextDirection, apple
var moveInterval = 0.1
var moveTimer = 0
var gameState = "playing"
function resetGame() {
var cx = Math.floor(gridW / 2)
var cy = Math.floor(gridH / 2)
snake = [
{x: cx, y: cy},
{x: cx-1, y: cy},
{x: cx-2, y: cy}
]
direction = {x:1, y:0}
nextDirection = {x:1, y:0}
spawnApple()
gameState = "playing"
moveTimer = 0
}
function spawnApple() {
apple = {x:Math.floor(Math.random()*gridW), y:Math.floor(Math.random()*gridH)}
// Re-spawn if apple lands on snake
for (var i=0; i<snake.length; i++)
if (snake[i].x === apple.x && snake[i].y === apple.y) { spawnApple(); return }
}
function wrap(pos) {
if (pos.x < 0) pos.x = gridW - 1
if (pos.x >= gridW) pos.x = 0
if (pos.y < 0) pos.y = gridH - 1
if (pos.y >= gridH) pos.y = 0
}
resetGame()
this.update = function(dt) {
if (gameState !== "playing") return
moveTimer += dt
if (moveTimer < moveInterval) return
moveTimer -= moveInterval
// Update direction
direction = {x: nextDirection.x, y: nextDirection.y}
// New head
var head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y}
wrap(head)
// Check collision with body
for (var i=0; i<snake.length; i++) {
if (snake[i].x === head.x && snake[i].y === head.y) {
gameState = "gameover"
return
}
}
// Place head
snake.unshift(head)
// Eat apple?
if (head.x === apple.x && head.y === apple.y) spawnApple()
else snake.pop()
}
this.hud = function() {
// Optional clear screen
draw.rectangle({x:0, y:0, width:config.width, height:config.height}, [0,0,0,1])
// Draw snake
for (var i=0; i<snake.length; i++) {
var s = snake[i]
draw.rectangle({x:s.x*cellSize, y:s.y*cellSize, width:cellSize, height:cellSize}, Color.green)
}
// Draw apple
draw.rectangle({x:apple.x*cellSize, y:apple.y*cellSize, width:cellSize, height:cellSize}, Color.red)
if (gameState === "gameover") {
var msg = "GAME OVER! Press SPACE to restart."
draw.text(msg, {x:0, y:config.height*0.5-10, width:config.width, height:20}, undefined, 0, Color.white)
}
}
// No immediate reversal
// "Up" means y=1, so going physically up on screen
this.inputs = {
up: function() {
if (direction.y !== -1) nextDirection = {x:0,y:1}
},
down: function() {
if (direction.y !== 1) nextDirection = {x:0,y:-1}
},
left: function() {
if (direction.x !== 1) nextDirection = {x:-1,y:0}
},
right: function() {
if (direction.x !== -1) nextDirection = {x:1,y:0}
},
space: function() {
if (gameState==="gameover") resetGame()
}
}
input.player[0].control(this)

View File

@@ -0,0 +1,5 @@
return {
title: "Tetris",
width:160,
height:144
}

270
examples/tetris/main.js Normal file
View File

@@ -0,0 +1,270 @@
var draw = use('draw2d')
var input = use('input')
var config = use('config')
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 = Object.keys(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[Math.floor(Math.random()*shapeKeys.length)]
// Make a copy of the shapes blocks
return {
type: key,
color: SHAPES[key].color,
blocks: SHAPES[key].blocks.map(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<blocks.length; 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<piece.blocks.length; 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<blocks.length; 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 (board[r].every(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 = Math.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 = piece.blocks.map(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 = Math.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<piece.blocks.length; 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}, undefined, 0, Color.white)
if (nextPiece) {
for (var i=0; i<nextPiece.blocks.length; 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}, undefined, 0, Color.white)
if (gameOver) {
draw.text("GAME OVER", {x:10, y:config.height*0.5-5, width:config.width-20, height:20}, undefined, 0, Color.red)
}
}