From 35470a151e99f851360a216290a615bad151211e Mon Sep 17 00:00:00 2001 From: fredericrous Date: Mon, 27 Apr 2020 12:15:30 +0100 Subject: [PATCH 1/2] improve ui, add 2 player local prepare the ground for 2 players bluetooth --- apps.json | 3 +- apps/pong/ChangeLog | 1 + apps/pong/app.js | 288 +++++++++++++++++++++++++++++++++----------- 3 files changed, 218 insertions(+), 74 deletions(-) diff --git a/apps.json b/apps.json index f167acf5d..5708e6d62 100644 --- a/apps.json +++ b/apps.json @@ -1486,11 +1486,12 @@ "name": "Pong", "shortName": "Pong", "icon": "pong.png", - "version": "0.01", + "version": "0.02", "description": "A clone of the Atari game Pong", "tags": "game", "type": "app", "allow_emulator": true, + "readme": "README.md", "storage": [ {"name":"pong.app.js","url":"app.js"}, {"name":"pong.img","url":"app-icon.js","evaluate":true} diff --git a/apps/pong/ChangeLog b/apps/pong/ChangeLog index 5560f00bc..6433ebce4 100644 --- a/apps/pong/ChangeLog +++ b/apps/pong/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: 2 players local + improve ai diff --git a/apps/pong/app.js b/apps/pong/app.js index 4531b3af8..272eaf2e7 100644 --- a/apps/pong/app.js +++ b/apps/pong/app.js @@ -8,6 +8,7 @@ * - Let's make pong, One Man Army Studios, Youtube * - Pong.js, KanoComputing, Github * - Coding Challenge #67: Pong!, The Coding Train, Youtube + * - Pixl.js Multiplayer Pong, espruino website */ const SCREEN_WIDTH = 240; @@ -15,6 +16,13 @@ const FPS = 16; const MAX_SCORE = 11; let scores = [0, 0]; let aiSpeedRandom = 0; +let winnerMessage = ''; + +const sound = { + ping: () => Bangle.beep(8, 466), + pong: () => Bangle.beep(8, 220), + fall: () => Bangle.beep(16*3, 494).then(_ => Bangle.beep(32*3, 3322)) +}; function Vector(x, y) { this.x = x; @@ -28,12 +36,18 @@ Vector.prototype.add = function (x) { const constrain = (n, low, high) => Math.max(Math.min(n, high), low); const random = (min, max) => Math.random() * (max - min) + min; -const intersects = (circ, rect) => { - var c1 = circ.pos, c2 = {x: circ.pos.x+circ.r, y: circ.pos.y+circ.r}; - var r1 = rect.pos, r2 = {x: rect.pos.x+rect.width*2, y: rect.pos.y+rect.height}; - return !(c1.x > r2.x || c2.x < r1.x || - c1.y > r2.y || c2.y < r1.y); -}; +const intersects = (circ, rect, right) => { + var c = circ.pos; + c.r = circ.r; + if (c.y - c.r < rect.pos.y + rect.height && c.y + c.r > rect.pos.y) { + if (right) { + return c.x + c.r > rect.pos.x - rect.width*2 && c.x < rect.pos.x + rect.width + } else { + return c.x - c.r < rect.pos.x + rect.width*2 && c.x > rect.pos.x - rect.width + } + } + return false; +} ///////////////////////////// Ball ////////////////////////////////////////// @@ -45,12 +59,26 @@ function Ball() { this.reset(); } -Ball.prototype.show = function () { +Ball.prototype.reset = function() { + this.speed = this.originalSpeed; + var x = scores[0] < scores[1] || (scores[0] === 0 && scores[1] === 0) ? -this.speed : this.speed; + var bounceAngle = Math.PI/6; + this.velocity = new Vector(x * Math.cos(bounceAngle), this.speed * -Math.sin(bounceAngle)); + this.pos = new Vector(SCREEN_WIDTH/2, random(0, SCREEN_WIDTH)); + this.ballReturn = 0; +}; +Ball.prototype.restart = function() { + this.reset(); + ai.pos = new Vector(SCREEN_WIDTH - ai.width*2, SCREEN_WIDTH/2 - ai.height/2); + player.pos = new Vector(player.width*2, SCREEN_WIDTH/2 - player.height/2); + this.pos = new Vector(SCREEN_WIDTH/2, SCREEN_WIDTH/2); +}; +Ball.prototype.show = function (invert) { if (this.prevPos != null) { - g.setColor(0); + g.setColor(invert ? -1 : 0); g.fillCircle(this.prevPos.x, this.prevPos.y, this.prevPos.r); } - g.setColor(-1); + g.setColor(invert ? 0 : -1); g.fillCircle(this.pos.x, this.pos.y, this.r); this.prevPos = { x: this.pos.x, @@ -58,55 +86,62 @@ Ball.prototype.show = function () { r: this.r }; }; -Ball.prototype.bouncePlayer = function (multiplyX, multiplyY, player) { +function bounceAngle(playerY, ballY, playerHeight, maxHangle) { + let relativeIntersectY = (playerY + (playerHeight/2)) - ballY; + let normalizedRelativeIntersectionY = relativeIntersectY / (playerHeight/2); + let bounceAngle = normalizedRelativeIntersectionY * maxHangle; + return { x: Math.cos(bounceAngle), y: -Math.sin(bounceAngle) }; +} +Ball.prototype.bouncePlayer = function (directionX, directionY, player) { + this.ballReturn++; this.speed = constrain(this.speed + 2, this.originalSpeed, this.maxSpeed); - var relativeIntersectY = (player.pos.y+(player.height/2)) - this.pos.y; - var normalizedRelativeIntersectionY = (relativeIntersectY/(player.height/2)); var MAX_BOUNCE_ANGLE = 4 * Math.PI/12; - var bounceAngle = normalizedRelativeIntersectionY * MAX_BOUNCE_ANGLE; - this.velocity.x = this.speed * Math.cos(bounceAngle) * multiplyX; - this.velocity.y = this.speed * -Math.sin(bounceAngle) * multiplyY; + var angle = bounceAngle(player.pos.y, this.pos.y, player.height, MAX_BOUNCE_ANGLE) + this.velocity.x = this.speed * angle.x * directionX; + this.velocity.y = this.speed * angle.y * directionY; + this.ballReturn % 2 === 0 ? sound.ping() : sound.pong(); }; -Ball.prototype.bounce = function (multiplyX, multiplyY, player) { +Ball.prototype.bounce = function (directionX, directionY, player) { if (player) - return this.bouncePlayer(multiplyX, multiplyY, player); + return this.bouncePlayer(directionX, directionY, player); - if (multiplyX) { - this.velocity.x = Math.abs(this.velocity.x) * multiplyX; + if (directionX) { + this.velocity.x = Math.abs(this.velocity.x) * directionX; } - if (multiplyY) { - this.velocity.y = Math.abs(this.velocity.y) * multiplyY; + if (directionY) { + this.velocity.y = Math.abs(this.velocity.y) * directionY; } }; -Ball.prototype.checkWallsCollision = function () { +Ball.prototype.fall = function (playerId) { + scores[playerId]++; + if (scores[playerId] >= MAX_SCORE) { + this.restart(); + state = 3; + if (playerId === 1) { + winnerMessage = startOption === 0 ? "AI Wins!" : "Player 2 Wins!"; + } else { + winnerMessage = startOption === 0 ? "You Win!" : "Player 1 Wins!"; + } + } else { + sound.fall(); + this.reset(); + } +}; +Ball.prototype.wallCollision = function () { if (this.pos.y < 0) { this.bounce(0, 1); } else if (this.pos.y > SCREEN_WIDTH) { this.bounce(0, -1); } else if (this.pos.x < 0) { - scores[1]++; - if (scores[1] >= MAX_SCORE) { - this.restart(); - state = 3; - winnerMessage = "AI Wins!"; - } else { - this.reset(); - } + this.fall(1); } else if (this.pos.x > SCREEN_WIDTH) { - scores[0]++; - if (scores[0] >= MAX_SCORE) { - this.restart(); - state = 3; - winnerMessage = "You Win!"; - } else { - this.reset(); - } + this.fall(0); } else { return false; } return true; }; -Ball.prototype.checkPlayerCollision = function (player) { +Ball.prototype.playerCollision = function (player) { if (intersects(this, player)) { if (this.pos.x < SCREEN_WIDTH/2) { this.bounce(1, 1, player); @@ -120,8 +155,8 @@ Ball.prototype.checkPlayerCollision = function (player) { } return false; }; -Ball.prototype.checkCollisions = function () { - return this.checkWallsCollision() || this.checkPlayerCollision(player) || this.checkPlayerCollision(ai); +Ball.prototype.collisions = function () { + return this.wallCollision() || this.playerCollision(player) || this.playerCollision(ai); }; Ball.prototype.updatePosition = function () { var elapsed = new Date().getTime() - this.lastUpdate; @@ -132,31 +167,20 @@ Ball.prototype.updatePosition = function () { Ball.prototype.update = function () { this.updatePosition(); this.lastUpdate = new Date().getTime(); - this.checkCollisions(); -}; -Ball.prototype.reset = function() { - this.speed = this.originalSpeed; - var x = scores[0] < scores[1] || (scores[0] === 0 && scores[1] === 0) ? -this.speed : this.speed; - var bounceAngle = Math.PI/6; - this.velocity = new Vector(x * Math.cos(bounceAngle), this.speed * -Math.sin(bounceAngle)); - this.pos = new Vector(SCREEN_WIDTH/2, random(0, SCREEN_WIDTH)); -}; -Ball.prototype.restart = function() { - ai.pos = new Vector(SCREEN_WIDTH - ai.width*2, SCREEN_WIDTH/2 - ai.height/2); - player.pos = new Vector(player.width*2, SCREEN_WIDTH/2 - player.height/2); - this.pos = new Vector(SCREEN_WIDTH/2, SCREEN_WIDTH/2); + this.collisions(); }; //////////////////////////// Player ///////////////////////////////////////// -function Player() { +function Player(right) { this.width = 4; this.height = 30; - this.pos = new Vector(this.width*2, SCREEN_WIDTH/2 - this.height/2); + this.pos = new Vector(right ? SCREEN_WIDTH-this.width : this.width, SCREEN_WIDTH/2 - this.height/2); this.acc = new Vector(0, 0); this.speed = 15; this.maxSpeed = 25; this.prevPos = null; + this.right = right; } Player.prototype.show = function () { if (this.prevPos != null) { @@ -196,11 +220,14 @@ function AI() { AI.prototype = Object.create(Player.prototype); AI.prototype.constructor = Player; AI.prototype.update = function () { - var y = ball.pos.y - (this.height/2 * aiSpeedRandom); - var yConstrained = constrain(y, 0, SCREEN_WIDTH-this.height); + var y = ball.pos.y - this.height/2; + var randomizedY = ball.ballReturn < 3 ? y : y + (aiSpeedRandom * this.height/2); + var yConstrained = constrain(randomizedY, 0, SCREEN_WIDTH-this.height); this.pos = new Vector(this.pos.x, yConstrained); }; +/////////////////////////////// Scenes //////////////////////////////////////// + function net() { var dashSize = 5; for (let y = dashSize/2; y < SCREEN_WIDTH; y += dashSize*2) { @@ -210,12 +237,6 @@ function net() { } } -var player = new Player(); -var ai = new AI(); -var ball = new Ball(); -var state = 0; -var prevScores = [0, 0]; - function drawScores() { let x1 = SCREEN_WIDTH/4-5; let x2 = SCREEN_WIDTH*3/4-5; @@ -233,10 +254,80 @@ function drawScores() { function drawGameOver() { g.setFont("Vector", 20); - g.drawString(winnerMessage, 75, SCREEN_WIDTH/2 - 10); + g.drawString(winnerMessage, startOption === 0 ? 55 : 75, SCREEN_WIDTH/2 - 10); } -function draw() { +function showControls(hide) { + g.setColor(hide ? 0 : -1); + g.setFont("Vector", 8); + var topArrowString = ` + ######## + ## + ## ## + ### ## + ### ## + ### +## +`; + + var arrows = [Graphics.createImage(topArrowString), Graphics.createImage(` + ## + ## +#################### + ## + ## +`), Graphics.createImage(topArrowString.split('\n').reverse().join('\n')) + ]; + + g.drawString('UP', 170, 50); + g.drawImage(arrows[0], 200, 40); + g.drawString('DOWN', 156, 120); + g.drawImage(arrows[1], 200, 120); + g.drawString('START', 152, 190); + g.drawImage(arrows[2], 200, 200); +} + +function drawStartScreen(hide) { + g.setColor(hide ? 0 : -1); + g.setFont("Vector", 10); + g.drawString("1 PLAYER", 95, 80); + g.drawString("2 PLAYERS", 95, 110); + + const ball1 = new Ball(); + ball1.prevPos = null; + ball1.pos = new Vector(87, 86); + ball1.show(hide || !(startOption === 0)); + + const ball2 = new Ball(); + ball2.prevPos = null; + ball2.pos = new Vector(87, 116); + ball2.show(hide || !(startOption === 1)); +} + +function drawStartTimer(count, callback) { + setTimeout(_ => { + player.show(); + ai.show(); + net(); + g.setColor(0); + g.fillRect(117-7, 115-7, 117+14, 115+14); + if (count >= 0) { + g.setFont("Vector", 10); + g.drawString(count+1, 115, 115); + g.setColor(-1); + g.drawString(count === 0 ? 'Go!' : count, 115 - (count === 0 ? 4: 0), 115); + drawStartTimer(count - 1, callback); + } else { + g.setColor(0); + g.fillRect(117-7, 115-7, 117+14, 115+14); + callback(); + } + }, 800); +} + +//////////////////////////////// Main ///////////////////////////////////////// + +function onFrame() { if (state === 1) { ball.update(); player.update(); @@ -261,22 +352,73 @@ function draw() { drawScores(); } +function startThatGame() { + player.show(); + ai.show(); + net(); + drawScores(); + drawStartTimer(3, () => setInterval(onFrame, 1000 / FPS)); +} + +var player = new Player(); +var ai; +var ball = new Ball(); +var state = 0; +var prevScores = [0, 0]; +var playerBle = null; +var startOption = 0; + g.clear(); g.setColor(0); g.fillRect(0,0,240,240); +showControls(); +setTimeout(() => { + showControls(true); + drawStartScreen(); +}, 2000); -setInterval(draw, 1000 / FPS); +////////////////////////////// Controls /////////////////////////////////////// -setWatch(o => o.state ? player.up() : player.stop(), BTN1, {repeat: true, edge: 'both'}); -setWatch(o => o.state ? player.down() : player.stop(), BTN3, {repeat: true, edge: 'both'}); -//setWatch(o => o.state ? player.down() : player.stop(), BTN5, {repeat: true, edge: 'both'}); +setWatch(o => { + if (state === 0) { + if (o.state) { + startOption = startOption === 0 ? startOption : startOption - 1; + drawStartScreen(); + } + } else o.state ? player.up() : player.stop(); +}, BTN1, {repeat: true, edge: 'both'}); +setWatch(o => { + if (state === 0) { + if (o.state) { + startOption = startOption === 1 ? startOption : startOption + 1; + drawStartScreen(); + } + } else o.state ? player.down() : player.stop(); +}, BTN2, {repeat: true, edge: 'both'}); setWatch(o => { state++; + clearInterval(); if (state >= 2) { - ball.restart(); g.setColor(0); - g.fillRect(0,0,240,240); + g.fillRect(0, 0, 240, 240); + ball.show(true); scores = [0, 0]; + playerBle = null; + ball = new Ball(); state = 1; + startThatGame(); + } else { + drawStartScreen(true); + showControls(true); + if (startOption === 1) { + ai = new Player(true); + startThatGame(); + } else { + ai = new AI(); + startThatGame(); + } } -}, BTN2, {repeat: true}); +}, BTN3, {repeat: true}); + +setWatch(o => startOption === 1 && (o.state ? ai.up() : ai.stop()), BTN4, {repeat: true, edge: 'both'}); +setWatch(o => startOption === 1 && (o.state ? ai.down() : ai.stop()), BTN5, {repeat: true, edge: 'both'}); From e03d33edc10fef8a720e0f51744d8608f0a4d8e7 Mon Sep 17 00:00:00 2001 From: fredericrous Date: Mon, 27 Apr 2020 14:13:26 +0100 Subject: [PATCH 2/2] add readme, remove impurity --- apps/pong/README.md | 28 ++++++++++++++++++++++++++++ apps/pong/app.js | 8 ++++---- 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 apps/pong/README.md diff --git a/apps/pong/README.md b/apps/pong/README.md new file mode 100644 index 000000000..ea4939539 --- /dev/null +++ b/apps/pong/README.md @@ -0,0 +1,28 @@ +# Pong + +A clone of the Atari game Pong + + + +## Features + +- Play against a dumb AI +- Play local Multiplayer against your friends + +## Controls + +Player's controls: +- UP: BTN1 +- DOWN: BTN2 +long press to move faster + +Restart a game: +- RESET: BTN3 + +Buttons for player 2: +- UP: BTN4 +- DOWN: BTN5 + +## Creator + + diff --git a/apps/pong/app.js b/apps/pong/app.js index 272eaf2e7..ba34d60b5 100644 --- a/apps/pong/app.js +++ b/apps/pong/app.js @@ -38,12 +38,12 @@ const constrain = (n, low, high) => Math.max(Math.min(n, high), low); const random = (min, max) => Math.random() * (max - min) + min; const intersects = (circ, rect, right) => { var c = circ.pos; - c.r = circ.r; - if (c.y - c.r < rect.pos.y + rect.height && c.y + c.r > rect.pos.y) { + var r = circ.r; + if (c.y - r < rect.pos.y + rect.height && c.y + r > rect.pos.y) { if (right) { - return c.x + c.r > rect.pos.x - rect.width*2 && c.x < rect.pos.x + rect.width + return c.x + r > rect.pos.x - rect.width*2 && c.x < rect.pos.x + rect.width } else { - return c.x - c.r < rect.pos.x + rect.width*2 && c.x > rect.pos.x - rect.width + return c.x - r < rect.pos.x + rect.width*2 && c.x > rect.pos.x - rect.width } } return false;