diff --git a/brickbreak/brickbreakclasses.js b/brickbreak/brickbreakclasses.js index 08f47fc..c296b98 100644 --- a/brickbreak/brickbreakclasses.js +++ b/brickbreak/brickbreakclasses.js @@ -22,6 +22,7 @@ export class Hud { export class Ball { constructor(world) { this.canvas = world.canvas; + this.ctx = world.ctx; this.x = this.canvas.width / 2; this.y = this.canvas.height - 30; this.ballRadius = 10; diff --git a/index.js b/index.js index 5b944de..5a14c0c 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,3 @@ -const brickBreakClasses = require('./brickbreak/brickbreakclasses'); -const snakeClasses = require('./snake/snakeclasses'); +const pongserverclasses = require('./pongserver/pongserverclasses'); -module.exports.brickbreak = brickBreakClasses; -module.exports.snake = snakeClasses; \ No newline at end of file +module.exports.pongserver = pongserverclasses; diff --git a/pongserver/README.md b/pongserver/README.md new file mode 100644 index 0000000..1e51a4e --- /dev/null +++ b/pongserver/README.md @@ -0,0 +1,111 @@ +## USAGE +An example setup could look like this: +``` +test-game +| public +----| example.html +----| example.js +| index.js +``` + +First, `npm install --save game-iverse` and `npm install --save socket.io`. Then our source could look like this: + +```example.html``` +```html + + + +``` + +```example.js``` +```js +import { World, Ball, Paddle, Hud } from '/pongserver/pongclasses.js'; +var world = new World(document.getElementById("canvas")); +var ball = new Ball(world); +var hud = new Hud(world); +var p1paddle = new Paddle(world); +var p2paddle = new Paddle(world); +p1paddle.keyUp; +p1paddle.keyDown; +var lastUpdate = performance.now(); +var socket = io('', {query: `width=${world.canvas.width}&height=${world.canvas.height}`}); +socket.on('update', function(state) { + p1paddle.setState(state.p1paddle); + p2paddle.setState(state.p2paddle); + ball.setState(state.ball); + hud.setState(state.hud); +}); +var draw = (time) => { + if (performance.now() - lastUpdate >= 30) { + lastUpdate = performance.now(); + socket.emit('update', {p1paddle: p1paddle.getState()}); + } + world.ctx.clearRect(0, 0, world.canvas.width, world.canvas.height); + ball.draw(); + p1paddle.draw(); + p2paddle.draw(); + hud.draw(); + requestAnimationFrame(draw); +} +requestAnimationFrame(draw); +``` + +```index.js``` +```js +const express = require('express'); +const app = express(); +const http = require('http'); +const server = http.Server(app); +const path = require('path'); +const io = require('socket.io')(server); +const gameiverse = require('../Game-iverse') +const port = 8000; + +app.set('port', port); +app.get('/', function(req, res) { + res.sendFile(path.join(__dirname, 'public/example.html')); +}); +app.use(express.static('public')) +app.use(express.static(path.join(__dirname, './node_modules/game-iverse'))) + +io.on('connection', (socket) => { + var world = new gameiverse.pongserver.World(socket.handshake.query.width, socket.handshake.query.height); + var ball = new gameiverse.pongserver.Ball(world); + var p1paddle = new gameiverse.pongserver.Paddle(world, 'player1'); + var p2paddle = new gameiverse.pongserver.AIPaddle(world, 'player2', ball); + var hud = new gameiverse.pongserver.Hud(); + + socket.on('update', (state) => { + p1paddle.setState(state.p1paddle); + }); + var tc = 0; + var tick = () => { + p1paddle.movement(); + p2paddle.movement(); + ball.boundaries([p1paddle, p2paddle], hud); + ball.movement(); + if (tc >= 6) { + socket.emit('update', { + p1paddle: p1paddle.getState(), + p2paddle: p2paddle.getState(), + hud: hud.getState(), + ball: ball.getState() + }); + tc = 0; + return; + } + tc += 1; + } + + var iv = setInterval(tick, 5); + + socket.on('disconnect', () => { + clearInterval(iv); + }); +}); + +server.listen(port, function() { +}); +``` + +You should now be able to run the game by visiting `localhost:8000`. diff --git a/pongserver/pongclasses.js b/pongserver/pongclasses.js new file mode 100644 index 0000000..2dc977d --- /dev/null +++ b/pongserver/pongclasses.js @@ -0,0 +1,113 @@ +export class World { + constructor(canvas) { + this.canvas = canvas; + this.ctx = this.canvas.getContext("2d"); + } +} + +export class Hud { + constructor(world) { + this.canvas = world.canvas; + this.ctx = world.ctx; + this.p1score = 0; + this.p2score = 0; + } + + draw() { + this.ctx.font = "16px Arial"; + this.ctx.fillStyle = "#0095DD"; + this.ctx.fillText("Player 1 Score: " + this.p1score + ", Player 2 Score: " + this.p2score, 8, 20); + } + + setState(state) { + this.p1score = state.p1score; + this.p2score = state.p2score; + } +} + +export class Paddle { + constructor(world) { + this.canvas = world.canvas; + this.ctx = world.ctx; + this.height = 75; + this.width = 10; + this.y = 0; + this.x = 0; + this.upPressed = false; + this.downPressed = false; + + this.keyDown = document.addEventListener( + "keydown", + e => this.keyDownHandler(e), + false + ); + + this.keyUp = document.addEventListener( + "keyup", + e => this.keyUpHandler(e), + false + ); + } + keyUpHandler(e) { + if (e.keyCode == 38) { + this.upPressed = false; + } else if (e.keyCode == 40) { + this.downPressed = false; + } + } + + keyDownHandler(e) { + if (e.keyCode == 38) { + this.upPressed = true; + } else if (e.keyCode == 40) { + this.downPressed = true; + } + } + getState() { + return { + upPressed: this.upPressed, + downPressed: this.downPressed + }; + } + + setState(state) { + this.y = state.y; + this.x = state.x; + } + + draw() { + this.ctx.beginPath(); + this.ctx.rect( + this.x, + this.y, + this.width, + this.height + ); + this.ctx.fillStyle = "#0095DD"; + this.ctx.fill(); + this.ctx.closePath(); + } +} + +export class Ball { + constructor(world) { + this.canvas = world.canvas; + this.ctx = world.ctx; + this.radius = 10; + this.x = 0; + this.y = 0; + } + + draw() { + this.ctx.beginPath(); + this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); + this.ctx.fillStyle = "#0095DD"; + this.ctx.fill(); + this.ctx.closePath(); + } + + setState(state) { + this.x = state.x; + this.y = state.y; + } +} diff --git a/pongserver/pongserverclasses.js b/pongserver/pongserverclasses.js new file mode 100644 index 0000000..e83e1a5 --- /dev/null +++ b/pongserver/pongserverclasses.js @@ -0,0 +1,164 @@ +class World { + constructor(width, height) { + this.width = width; + this.height = height; + } +} + +class Hud { + constructor() { + this.p1score = 0; + this.p2score = 0; + } + + getState() { + return { + p1score: this.p1score, + p2score: this.p2score + } + } +} + +class Ball { + constructor(world) { + this.world = world; + this.radius = 10; + this.reset(); + } + + reset() { + this.x = this.world.width / 2; + this.y = this.world.height / 2; + this.dx = 1; + this.dy = -1; + this.speed = 25; + this.lastUpdate = process.hrtime.bigint(); + } + + nextPos() { + var dt = Number(process.hrtime.bigint() - this.lastUpdate) / 1e9; + return { + x: this.x + this.dx * this.speed * dt, + y: this.y + this.dy * this.speed * dt + } + } + + boundaries(paddles, hud) { + const clamp = (v, min, max) => v < min? min: v > max? max: v; + var nextPos = this.nextPos(); + if (nextPos.y - this.radius <= 0 || nextPos.y + this.radius >= this.world.height) { + // collision with top or bottom of play area + this.dy = -this.dy; + } + for (var paddle of paddles) { + var dx = nextPos.x - clamp(nextPos.x, paddle.x, paddle.x + paddle.width); + var dy = nextPos.y - clamp(nextPos.y, paddle.y, paddle.y + paddle.height); + if (dx**2 + dy**2 < this.radius**2) { + // collision with a paddle + this.dx = -this.dx; + return; + } + } + if (nextPos.x - this.radius <= 0) { + hud.p2score += 1; + this.reset(); + } + else if (nextPos.x + this.radius >= this.world.width) { + hud.p1score += 1; + this.reset(); + } + } + + movement() { + var nextPos = this.nextPos(); + this.lastUpdate = process.hrtime.bigint(); + this.x = nextPos.x; + this.y = nextPos.y; + this.speed += this.speed*0.01; + } + + getState() { + return { + x: this.x, + y: this.y + }; + } +} + +class Paddle { + constructor(world, player) { + this.world = world; + this.height = 75; + this.width = 10; + this.x = player == "player1"? 0: this.world.width - this.width; + this.speed = 100; + this.reset(); + } + + reset() { + this.lastUpdate = process.hrtime.bigint(); + this.upPressed = false; + this.downPressed = false; + this.y = this.world.height - this.height / 2; + } + + getState() { + return { + x: this.x, + y: this.y + } + } + + setState(state) { + this.upPressed = state.upPressed; + this.downPressed = state.downPressed; + } + + nextPos() { + var y = this.y; + var dt = Number(process.hrtime.bigint() - this.lastUpdate) / 1e9; + if (this.upPressed) { + y -= this.speed * dt; + } + else if (this.downPressed) { + y += this.speed * dt; + } + return { + x: this.x, + y: y + } + } + + movement() { + var nextPos = this.nextPos(); + this.lastUpdate = process.hrtime.bigint(); + const clamp = (v, min, max) => v < min? min: v > max? max: v; + this.y = clamp(nextPos.y, 0, this.world.height - this.height); + } +} + +class AIPaddle extends Paddle { + constructor(world, player, ball) { + super(world, player); + this.ball = ball; + } + nextPos() { + super.upPressed = false; + super.downPressed = false; + if (this.y + this.height / 2 > this.ball.y) { + super.upPressed = true; + } + else if (this.y + this.height / 2 < this.ball.y) { + super.downPressed = true; + } + return super.nextPos(); + } +} + +module.exports = { + AIPaddle, + Paddle, + Hud, + Ball, + World +};