From b41f00458bd71456ca05181b9c228e4af8546d52 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 4 Feb 2025 07:38:43 -0600 Subject: [PATCH] add pong, snake, and tetris examples --- examples/pong/config.js | 5 + examples/pong/main.js | 85 ++++++++++++ examples/snake/config.js | 5 + examples/snake/main.js | 118 +++++++++++++++++ examples/tetris/config.js | 5 + examples/tetris/main.js | 270 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 488 insertions(+) create mode 100644 examples/pong/config.js create mode 100644 examples/pong/main.js create mode 100644 examples/snake/config.js create mode 100644 examples/snake/main.js create mode 100644 examples/tetris/config.js create mode 100644 examples/tetris/main.js diff --git a/examples/pong/config.js b/examples/pong/config.js new file mode 100644 index 00000000..7c612689 --- /dev/null +++ b/examples/pong/config.js @@ -0,0 +1,5 @@ +return { + title: "Pong", + width: 858, + height: 525 +} diff --git a/examples/pong/main.js b/examples/pong/main.js new file mode 100644 index 00000000..63a6a52a --- /dev/null +++ b/examples/pong/main.js @@ -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 && lbottom1 && bleft2 && lbottom2 && bconfig.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) +} diff --git a/examples/snake/config.js b/examples/snake/config.js new file mode 100644 index 00000000..79dbdf2a --- /dev/null +++ b/examples/snake/config.js @@ -0,0 +1,5 @@ +return { + title: "Snake", + width: 600, + height: 600 +} diff --git a/examples/snake/main.js b/examples/snake/main.js new file mode 100644 index 00000000..f4562192 --- /dev/null +++ b/examples/snake/main.js @@ -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= 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 [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=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=0) board[y][x] = piece.color + } +} + +// Rotate 90° clockwise +function rotate(blocks) { + // (x,y) => (y,-x) + for (var i=0; i=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 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