JS Ships
Creando un multiplayer game con Vanilla JavaScript,
NodeJS y programación reactiva







V.2



CucJS
¿Que nos une?


- Nos gusta Javascript!!!
- Queremos llevar nuestra pasión a todos.
- Queremos una comunidad gigantesca
- Queremos mejorar el nivel
- Queremos hacerlos relevantes
- Abrir puertas a oportunidades de investigation, entretenimiento, estudio y trabajo.
JavaScript Showcase
Web apps



NodeJS + React

NodeJS + EmberJS

NodeJS + React

AngularJS

VueJS

NodeJS + MongoDB
JavaScript Showcase
Apps multiplataforma y móviles nativas



Electron

Ionic

React Native

Electron

React Native

React Native
JavaScript Showcase
Task runners, module bundlers
y utilidades para alto rendimiento





JavaScript Showcase







V.2



JS Ships
Creando un multiplayer game con Vanilla JavaScript,
NodeJS y programación reactiva


JS Ships
¿Como surgió?


- Charlas entretenidas
- Posiblemente interactivas
- Que enseñen algo nuevo
- Y que sean interesantes (por favor 🙄)
JS Ships
¿Cual es el objetivo?


- Mostrar de manera practica conceptos nuevos
- Aplicación de nuevas tecnologías
- Interactividad
JS Ships
¿Que encontraras?


- Vanilla JS
- Código altamente compatible
- Pocas librerías
- Código que puede lograr verse mas limpio y modular (es intencional 😉)
JS Ships(V1.1.0): Front-end


function ScreenController(screenId) {
this.canvas = document.getElementById(screenId);
this.height = this.canvas.height;
this.width = this.canvas.width;
var ship = new Ship(this.height / 2, this.width / 2, this.height * 0.1)
this.drawBackground = function (ctx) { //Draws the background grid. }
this.render = function () {
var ctx = this.canvas.getContext("2d"); //can be "webgl" or "webgl2"
this.drawBackground(ctx)
ship.draw(ctx)
}
} <!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<canvas id="my-screen" width="1000" height="1000"></canvas>
<script src="assets/modules/ship/Ship.js"></script>
<script src="assets/modules/screen/ScreenController.js"></script>
<script src="assets/modules/main/main.js"></script>
</body>
</html>JS Ships(V1.1.0): Front-end


function Ship(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.angle = Math.random() * 360;
this.draw = function (ctx) { //draws a red triangle rotated "this.angle" degrees.
ctx.beginPath();
ctx.lineJoin = "round";
var offset = this.size / 2
ctx.save(); // saves the coordinate system
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.moveTo(0, -offset);
ctx.lineTo(-offset, offset);
ctx.lineTo(offset, offset);
ctx.lineTo(0, -offset);
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
ctx.restore();
}
}JS Ships(V1.1.0): Front-end


function startApp() {
var screenController = new ScreenController("my-screen");
var canvas = screenController.canvas;
canvas.style.left = ((window.innerWidth / 2) - (canvas.clientWidth / 2)) + "px";
screenController.render();
}JS Ships(V1.1.0): Back-end


var express = require('express')
var app = express()
console.log(process.cwd())
app.use(express.static("src/client"))
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
})JS Ships(V1.1.0)


JS Ships(V1.2.1): Front-end


function startApp() {
var screenController = new ScreenController("my-screen");
var canvas = screenController.canvas;
canvas.style.left = ((window.innerWidth / 2) - (canvas.clientWidth / 2)) + "px";
screenController.render();
}function startApp() {
var screenController = new ScreenController("my-screen");
var canvas = screenController.canvas;
canvas.style.left = ((window.innerWidth / 2) - (canvas.clientWidth / 2)) + "px";
setInterval(function () {
screenController.render()
}, 20)
}JS Ships(V1.2.1): Front-end


function ScreenController(screenId) {
var screenController = this;
this.canvas = document.getElementById(screenId);
this.height = this.canvas.height;
this.width = this.canvas.width;
var ship = new Ship(this.height / 2, this.width / 2, this.height * 0.05);
function notifyMoveShip(posX, posY) {
//transform coordinates in window to coordinates in the html canvas object
posX = posX - screenController.canvas.offsetLeft;
posY = posY - screenController.canvas.offsetTop;
//transform coordinates in canvas to coordinates in the canvas context.
posX = (posX * screenController.width) / screenController.canvas.clientWidth;
posY = (posY * screenController.height) / screenController.canvas.clientHeight;
ship.translate(posX, posY);
}
function notifyShot() { console.log("shoot"); }
this.canvas.addEventListener("mousemove", function (event) {
notifyMoveShip(event.pageX, event.pageY);
})
this.canvas.addEventListener("touchmove", function (event) {
if (event.touches.length > 1) {
return notifyShot();
}
var touch = event.touches[0];
notifyMoveShip(touch.pageX, touch.pageY);
})
this.canvas.addEventListener("click", notifyShot);
//...
this.render = function () {
var ctx = this.canvas.getContext("2d");
this.drawBackground(ctx);
ship.draw(ctx);
}
}JS Ships(V1.2.1): Front-end


function Ship(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.angle = 0;
this.draw = function (ctx) { //...Draws the ship in ctx }
this.translate = function (posX, posY) {
//transform coordinates using the middle of canvas as (0, 0)
var tPosX = posX - this.x;
var tPosY = -(posY - this.y);
this.angle = -Math.atan2(tPosY, tPosX) + (90 * Math.PI / 180);
this.x += 4*Math.sin(this.angle)
this.y -= 4*Math.cos(this.angle)
}
}JS Ships(V1.2.1)


JS Ships(V1.3.1): Front-end


function Bullet(startX, startY, angle, size) {
var bullet = this;
this.x = startX;
this.y = startY;
this.size = size;
this.angle = angle;
setInterval(function () {
bullet.translate()
}, 20)
this.translate = function () {
this.x += 5 * Math.sin(this.angle);
this.y -= 5 * Math.cos(this.angle);
};
this.draw = function (ctx) { //Draws a little blue triangle. };
}JS Ships(V1.3.1): Front-end


function Ship(x, y, size) {
var ship = this;
this.x = x;
this.y = y;
this.size = size;
this.angle = 0;
this.bullets = [];
this.canShoot = true;
//...
this.translate = function (posX, posY) {
//transform coordinates using the (0, 0) point of the ship
var tPosX = posX - this.x;
var tPosY = -(posY - this.y);
this.angle = -Math.atan2(tPosY, tPosX) + (90 * Math.PI / 180);
this.x += 2 * Math.sin(this.angle);
this.y -= 2 * Math.cos(this.angle);
};
this.shoot = function () {
if (this.canShoot) {
this.canShoot = false;
this.bullets.push(new Bullet(this.x, this.y, this.angle, this.size * 0.4));
setTimeout(function () {
ship.canShoot = true;
}, 500)
}
};
}JS Ships(V1.3.1)


JS Ships(V1.4.0)



JS Ships(V1.4.0)


- Controlador de Pantalla (ScreenController)
- 1 Nave
- Proyectiles
-
¡Todo en el front!
JS Ships(V1.4.0)



JS Ships(V1.4.0)



JS Ships
Programación Reactiva


- Módulos aislados y responsabilidades bien definidas.
- Comportamiento que reacciona a notificaciones.
JS Ships
Programación Reactiva
(Solución convencional)



JS Ships
Programación Reactiva



JS Ships
Programación Reactiva (No patron observador)



JS Ships(V1.4.0): Front-end


<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>
<canvas id="my-screen"></canvas>
<script src="/socket.io/socket.io.js"></script>
<script src="assets/modules/ship/Bullet.js"></script>
<script src="assets/modules/ship/Ship.js"></script>
<script src="assets/modules/screen/ScreenController.js"></script>
<script src="assets/modules/main/main.js"></script>
</body>
</html>JS Ships(V1.4.0): Front-end


function startApp() {
var socket = io();
socket.on("load-settings", function (settings) {
var screenController = new ScreenController("my-screen", socket);
var canvas = screenController.canvas;
canvas.width = settings.canvas.width;
canvas.height = settings.canvas.height;
canvas.style.left = ((window.innerWidth / 2) - (canvas.clientWidth / 2)) + "px";
setInterval(function () {
screenController.render();
}, 20);
});
}JS Ships(V1.4.0): Front-end


function ScreenController(screenId, socket) {
var screenController = this;
this.canvas = document.getElementById(screenId);
this.ships = [];
function notifyMoveShip(posX, posY) {
//transform coordinates in window to coordinates in the html canvas object
posX = posX - screenController.canvas.offsetLeft;
posY = posY - screenController.canvas.offsetTop;
//transform coordinates in canvas to coordinates in the canvas context.
posX = (posX * screenController.canvas.width) / screenController.canvas.clientWidth;
posY = (posY * screenController.canvas.height) / screenController.canvas.clientHeight;
socket.emit("ship::translate", {x: posX, y: posY})
}
function notifyShot() {
socket.emit("ship::shoot")
}
this.canvas.addEventListener("mousemove", function (event) {
notifyMoveShip(event.pageX, event.pageY);
});
this.canvas.addEventListener("touchmove", function (event) {
event.preventDefault();
if (event.touches.length > 1) {
return notifyShot();
}
var touch = event.touches[0];
notifyMoveShip(touch.pageX, touch.pageY);
});
this.canvas.addEventListener("click", notifyShot);
socket.on("update-ships-state", function (ships) {
screenController.ships = ships;
})
this.drawBackground = function (ctx) { //... }
this.render = function () {
var ctx = this.canvas.getContext("2d");
this.drawBackground(ctx)
this.ships.forEach(function (ship) {
ship.size = screenController.canvas.height * 0.03;
ship.isMyShip = ship.socket_id === socket.id;
drawShip(ctx, ship)
})
}
}JS Ships(V1.4.0): Front-end


function drawShip(ctx, ship) {
var offset = ship.size / 2;
ctx.save(); // saves the coordinate system
//...
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(0, -offset, ship.size * 0.2, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
ship.bullets.forEach(function (bullet) {
bullet.size = ship.size * 0.4;
drawBullet(ctx, bullet)
})
}JS Ships(V1.4.0): Back-end


var ships = []
io.on("connection", function (socket) {
socket.emit("load-settings", settings);
var ship = new Ship(socket);
ships.push(ship);
socket.on("ship::translate", function (pointerPosition) {
ship.translate(pointerPosition.x, pointerPosition.y);
});
socket.on("ship::shoot", function () {
ship.shoot();
});
socket.on('disconnect', function(){
var index = ships.indexOf(ship);
ships.splice(index, 1);
});
})
setInterval(function () {
io.emit("update-ships-state", ships.map(function (ship) {
return ship.toPlainObjet()
}))
}, 20)JS Ships(V1.4.0): Back-end


function Ship(socket) {
var ship = this;
this.socket_id = socket.id;
this.x = 100 + (Math.random() * (settings.canvas.width - 200));
this.y = 100 + (Math.random() * (settings.canvas.height - 200));
this.size = settings.canvas.width * 0.05;
this.angle = 0;
this.bullets = [];
this.canShoot = true;
this.translate = function (posX, posY) { //... };
this.shoot = function () {
//...
this.bullets.push(new Bullet(this.x, this.y, this.angle));
//...
};
this.toPlainObjet = function () {
return {
socket_id: this.socket_id,
x: this.x,
y: this.y,
angle: this.angle,
bullets: this.bullets.map(function (bullet) {
return bullet.toPlainObjet();
}),
canShoot: this.canShoot
}
}
}
module.exports = ShipJS Ships(V1.4.0): Back-end


function Bullet(startX, startY, angle) {
var bullet = this;
this.x = startX;
this.y = startY;
this.angle = angle;
setInterval(function () {
bullet.translate();
}, 20)
this.translate = function () {
this.x += 5 * Math.sin(this.angle);
this.y -= 5 * Math.cos(this.angle);
};
this.toPlainObjet = function () {
return {
x: this.x,
y: this.y,
angle: this.angle
}
}
}
module.exports = BulletJS Ships(V1.4.0)


JS Ships(v1.5.1)
Tiempo Real



JS Ships(v1.5.1)
Tiempo Real


setInterval(function () {
io.emit("update-ships-state", ships.map(function (ship) {
return ship.toPlainObjet()
}))
}, 20)
JS Ships(V1.5.1): Front-end


function ScreenController(screenId, socket) {
var screenController = this;
this.canvas = document.getElementById(screenId);
this.ships = [];
this.bullets = [];
this.gameStatus = "playing";
function notifyMoveShip(posX, posY) {
//...
socket.emit("ship::translate", {x: posX, y: posY})
}
function notifyShot() {
socket.emit("ship::shoot")
}
/...
socket.on("load-current-targets", function (targets) {
targets.forEach(function (target) {
if (target.type === "Ship") {
screenController.ships.push(target);
} else {
screenController.bullets.push(target);
}
});
});
socket.on("ship::add", function (ship) {
ship.isMyShip = ship.socket_id === socket.id;
screenController.gameStatus = "playing";
screenController.ships.push(ship);
});
socket.on("ship::update", function (ship) {
var existingShip = findInArray(screenController.ships, "socket_id", ship.socket_id);
for (var key in ship) {
existingShip[key] = ship[key];
}
});
socket.on("ship::remove", function (shipSocketId) {
var index = findIndexArray(screenController.ships, "socket_id", shipSocketId);
var ship = screenController.ships[index];
screenController.ships.splice(index, 1);
if (ship.isMyShip) {
screenController.gameStatus = "looser";
} else if (screenController.gameStatus !== "looser" && screenController.ships.length === 1) {
screenController.gameStatus = "winner";
}
});
socket.on("bullet::add", function (bullet) {
screenController.bullets.push(bullet);
});
socket.on("bullet::update", function (bullet) {
var existingBullet = findInArray(screenController.bullets, "id", bullet.id);
for (var key in bullet) {
existingBullet[key] = bullet[key];
}
});
socket.on("bullet::remove", function (bulletId) {
var index = findIndexArray(screenController.bullets, "id", bulletId);
screenController.bullets.splice(index, 1);
});
this.render = function () {
/...
}
}JS Ships(V1.5.1)


JS Ships
Creando un multiplayer game con Vanilla JavaScript,
NodeJS y programación reactiva


Ships JS
By CucutaJS
Ships JS
- 94