add pong, snake, and tetris examples
This commit is contained in:
5
examples/pong/config.js
Normal file
5
examples/pong/config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
return {
|
||||
title: "Pong",
|
||||
width: 858,
|
||||
height: 525
|
||||
}
|
||||
85
examples/pong/main.js
Normal file
85
examples/pong/main.js
Normal 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
5
examples/snake/config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
return {
|
||||
title: "Snake",
|
||||
width: 600,
|
||||
height: 600
|
||||
}
|
||||
118
examples/snake/main.js
Normal file
118
examples/snake/main.js
Normal 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)
|
||||
5
examples/tetris/config.js
Normal file
5
examples/tetris/config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
return {
|
||||
title: "Tetris",
|
||||
width:160,
|
||||
height:144
|
||||
}
|
||||
270
examples/tetris/main.js
Normal file
270
examples/tetris/main.js
Normal 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 shape’s 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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user