diff --git a/package.json b/package.json new file mode 100644 index 0000000..bf9a054 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "http-server-practice", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node ./src/server.js", + "build": "webpack --config webpack.config.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@babel/preset-react": "^7.0.0", + "css-loader": "^3.2.0", + "extract-text-webpack-plugin": "^3.0.2", + "nodemon": "^1.19.1", + "react": "^16.9.0", + "react-dom": "^16.9.0", + "style-loader": "^1.0.0", + "websocket": "^1.0.29" + }, + "devDependencies": { + "@babel/core": "^7.6.0", + "@babel/preset-env": "^7.6.0", + "babel-core": "^6.26.3", + "babel-loader": "^8.0.6", + "babel-preset-env": "^1.7.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.8.0", + "script-loader": "^0.7.2", + "webpack": "^4.40.1", + "webpack-cli": "^3.3.7" + } +} diff --git a/public/client.js b/public/client.js new file mode 100644 index 0000000..d8e1080 --- /dev/null +++ b/public/client.js @@ -0,0 +1,13 @@ +var W3CWebSocket = require('websocket').w3cwebsocket; + +var client = new W3CWebSocket('ws://localhost:3000/', 'echo-protocol'); + +client.onerror = e => { + console.log('Connection Error', e); +}; + +client.onclose = () => { + console.log('echo-protocol Client Closed'); +}; + +module.exports = client; \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..4aca288 --- /dev/null +++ b/public/index.html @@ -0,0 +1,10 @@ + + + + + + +
+ + + \ No newline at end of file diff --git a/public/src/App.js b/public/src/App.js new file mode 100644 index 0000000..629fc0b --- /dev/null +++ b/public/src/App.js @@ -0,0 +1,28 @@ +import React, { useState, useLayoutEffect } from 'react'; +import Car from './components/Car' +import Canvas from './components/Canvas'; + + +export default props => { + const [otherPlayers,setOtherPlayers] = useState([...props.otherPlayerData]); + + + document.addEventListener('add-player', (event) => { + setOtherPlayers([...otherPlayers,]) + }) + + useLayoutEffect(() => { + },[otherPlayers]) + return ( + + + {otherPlayers.map((player,index) => { + return ( + + {player} + + ) + })} + + ); +} \ No newline at end of file diff --git a/public/src/components/Canvas/css/canvas.css b/public/src/components/Canvas/css/canvas.css new file mode 100644 index 0000000..4b67825 --- /dev/null +++ b/public/src/components/Canvas/css/canvas.css @@ -0,0 +1,10 @@ +.canvas-main { + height: 100vh; + width: 100vw; + background-color: grey; +} + +* { + padding: 0px; + margin: 0px; +} \ No newline at end of file diff --git a/public/src/components/Canvas/index.js b/public/src/components/Canvas/index.js new file mode 100644 index 0000000..b827c6f --- /dev/null +++ b/public/src/components/Canvas/index.js @@ -0,0 +1,63 @@ +import React, {useState, useLayoutEffect} from 'react'; +const client = require('../../../client.js'); + + + +import './css/canvas.css'; + +const Canvas = props => { + const { angle:a, xPos:x, yPos:y, id } = props; + const [angle, setAngle] = useState(a); + const [xPos, setXPos] = useState(x); + const [yPos,setYPos] = useState(y); + + useLayoutEffect(() => { + },[]) + + useLayoutEffect(() => { + console.log('xPos',xPos, 'yPos',yPos,'angle', angle,"cosine sine values", Math.cos(angle* Math.PI / 180.0),Math.sin(angle* Math.PI / 180.0)); + const car = document.getElementById(id).style; + car.left = `${xPos}px`; + car.top = `${yPos}px`; + car.transform = `rotate(${angle}deg)`; + client.send(JSON.stringify({action:"UPDATE_PLAYER",data:{id,xPos,yPos,angle}})); + },[xPos,yPos,angle]); + + + const handleKeyPress = event => { + + const { keyCode } = event; + + const xDirection = Math.cos(angle* Math.PI / 180.0); + const yDirection = Math.sin(angle* Math.PI / 180.0) + + switch(keyCode) { + case 37: + setAngle(angle-5); + break; + case 38: + setXPos(xPos+5*xDirection); + setYPos(yPos+5*yDirection); + + break; + case 39: + setAngle(angle+5); + break; + case 40: + setXPos(xPos-5*xDirection); + setYPos(yPos-5*yDirection); + break; + default: + + } + + + } + return ( +
+ {props.children} +
+ ); +} + +export default Canvas; \ No newline at end of file diff --git a/public/src/components/Car/css/car.css b/public/src/components/Car/css/car.css new file mode 100644 index 0000000..aee8927 --- /dev/null +++ b/public/src/components/Car/css/car.css @@ -0,0 +1,8 @@ +.car-main { + position: relative; + background: linear-gradient(0.25turn, #3f87a6, #ebf8e1, #f69d3c); + height: 12px; + width: 30px; + transition: .15s; + transform:rotate(0deg); +} \ No newline at end of file diff --git a/public/src/components/Car/index.js b/public/src/components/Car/index.js new file mode 100644 index 0000000..6ab7220 --- /dev/null +++ b/public/src/components/Car/index.js @@ -0,0 +1,18 @@ +import React from 'react'; + +import './css/car.css'; +const Car = props => { + const { id, yPos, xPos,angle } = props; + + + + + return ( +
+ +
+ ); +} + + +export default Car; \ No newline at end of file diff --git a/public/src/index.js b/public/src/index.js new file mode 100644 index 0000000..d6e602c --- /dev/null +++ b/public/src/index.js @@ -0,0 +1,71 @@ +import React from 'react'; +const client = require('../client.js'); +import ReactDOM from 'react-dom'; +import Car from './components/Car'; +import App from './App'; + + +client.onopen = () => { + console.log("Browser connected to the server!!"); + +} + +function isEquivalent(a, b) { + // Create arrays of property names + var aProps = Object.getOwnPropertyNames(a); + var bProps = Object.getOwnPropertyNames(b); + + // If number of properties is different, + // objects are not equivalent + if (aProps.length != bProps.length) { + return false; + } + + for (var i = 0; i < aProps.length; i++) { + var propName = aProps[i]; + + // If values of same property are not equal, + // objects are not equivalent + if (a[propName] !== b[propName]) { + return false; + } + } + + // If we made it this far, objects + // are considered equivalent + return true; +} + + +client.onmessage = (e) => { + // console.log(e) + const {action, data, otherPlayerData} = JSON.parse(e.data); + switch(action){ + case "INIATE": + console.log("iniate") + const otherCars = otherPlayerData.map(other => !isEquivalent(data,other) && ) + ReactDOM.render(,document.getElementById('root')); + break; + case "NEW_PLAYER": + console.log("new-player") + document.dispatchEvent(new CustomEvent('add-player',{ + detail: { + data + } + })); + break; + case "UPDATE_CANVAS": + console.log('update') + const modifiedCar = document.getElementById(data.id); + modifiedCar.style.top = data.yPos; + modifiedCar.style.left = data.xPos; + modifiedCar.style.transform = `rotate(${data.angle}deg)` + break; + default: + console.log(data); + } + +}; + + + \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..fda568c --- /dev/null +++ b/server.js @@ -0,0 +1,204 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const WebSocketServer = require('websocket').server; + +const indexPath = path.join(__dirname,'/public/index.html'); +const jsPath = path.join(__dirname,'../dist/bundle.js'); +const cssPath = path.join(__dirname,'../dist/style.css'); + + + + +let carData = []; +let clients = [] + + + + + +const server = http.createServer((req,res) => { + if(req.url==="/"){ + fs.readFile(indexPath, (err, file) => { + if(err) { + res.send(500,{error: err}); + } + res.writeHeader(200, {"Content-Type": "text/html"}); + res.write(file); + res.end() + + }) + } + + if(req.url==="/bundle.js") { + fs.readFile(jsPath, (err, file) => { + if(err) { + console.log("Error getting the javaScript") + } + res.writeHeader(200, {"Content-Type": "text/javascript"}); + res.write(file); + res.end(); + }) + } + + + if(req.url==="/styles.css") { + fs.readFile(cssPath, (err, file ) => { + if(err) { + console.log("Error getting the css") + } + res.writeHeader(200, {"Content-Type": "text/css"}); + res.write(file); + res.end(); + }) + + + } +}); +server.listen(3000,'127.0.0.1'); + + +const broadcastExcept = (action,data,connection) => { + clients.map( socket => { + + if(!isEquivalent(connection,socket)) + socket.send(JSON.stringify( + { + action, + data + })); + }) +} + +const broadcastAll = (action,data) => { + clients.map( socket => { + socket.send(JSON.stringify( + { + action, + data + })); + }) +} + +const updateCars = (newData) => { + return carData.map(car => { + if(car.id === newData.id){ + return newData; + } + + return car; + }) +} + + +const webSocketServer = new WebSocketServer({httpServer:server}); + +webSocketServer.on('request', request => { + const data = { + id: uuid(), + xPos: randomXPos(), + yPos: randomXPos(), + angle: randomAngle() + } + + broadcastAll("NEW_PLAYER",data); + + var connection = request.accept('echo-protocol', request.origin); + + console.log((new Date()) + ' Connection accepted.'); + + + + carData.push(data); + console.log('8888888888888888888888888888888',carData); + connection.send(JSON.stringify( + { + action: 'INIATE', + data, + otherPlayerData:carData + })) + + + // setInterval(() => { + // connection.send(JSON.stringify({ + // action:"test", + // data:data.id + // })) + // }, 1000); + + clients.push(connection); + connection.on('message', message => { + const _message = JSON.parse(message.utf8Data); + switch(_message.action){ + case "UPDATE_PLAYER": + carData = updateCars(_message.data) + console.log("New Canvas Positions", carData); + broadcastExcept("UPDATE_CANVAS",_message.data,connection); + break; + default: + // console.log(_message); + } + + }); + + + connection.on('close', (reasonCode, description) => { + console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.'); + }); +}); + + + + + +const uuid = () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +const randomXPos = () => { + return Math.floor((Math.random() * 200) + 25); +} + +const randomYPos = () => { + return Math.floor((Math.random() * 200) + 25); +} + + + +const randomAngle = () => { + return Math.floor((Math.random() * 360)); +} + +const randomColor = () => { + +} + + +function isEquivalent(a, b) { + // Create arrays of property names + var aProps = Object.getOwnPropertyNames(a); + var bProps = Object.getOwnPropertyNames(b); + + // If number of properties is different, + // objects are not equivalent + if (aProps.length != bProps.length) { + return false; + } + + for (var i = 0; i < aProps.length; i++) { + var propName = aProps[i]; + + // If values of same property are not equal, + // objects are not equivalent + if (a[propName] !== b[propName]) { + return false; + } + } + + // If we made it this far, objects + // are considered equivalent + return true; +} \ No newline at end of file diff --git a/src/public/client.js b/src/public/client.js new file mode 100644 index 0000000..d8e1080 --- /dev/null +++ b/src/public/client.js @@ -0,0 +1,13 @@ +var W3CWebSocket = require('websocket').w3cwebsocket; + +var client = new W3CWebSocket('ws://localhost:3000/', 'echo-protocol'); + +client.onerror = e => { + console.log('Connection Error', e); +}; + +client.onclose = () => { + console.log('echo-protocol Client Closed'); +}; + +module.exports = client; \ No newline at end of file diff --git a/src/public/index.html b/src/public/index.html new file mode 100644 index 0000000..4aca288 --- /dev/null +++ b/src/public/index.html @@ -0,0 +1,10 @@ + + + + + + +
+ + + \ No newline at end of file diff --git a/src/public/src/App.js b/src/public/src/App.js new file mode 100644 index 0000000..629fc0b --- /dev/null +++ b/src/public/src/App.js @@ -0,0 +1,28 @@ +import React, { useState, useLayoutEffect } from 'react'; +import Car from './components/Car' +import Canvas from './components/Canvas'; + + +export default props => { + const [otherPlayers,setOtherPlayers] = useState([...props.otherPlayerData]); + + + document.addEventListener('add-player', (event) => { + setOtherPlayers([...otherPlayers,]) + }) + + useLayoutEffect(() => { + },[otherPlayers]) + return ( + + + {otherPlayers.map((player,index) => { + return ( + + {player} + + ) + })} + + ); +} \ No newline at end of file diff --git a/src/public/src/components/Canvas/css/canvas.css b/src/public/src/components/Canvas/css/canvas.css new file mode 100644 index 0000000..4b67825 --- /dev/null +++ b/src/public/src/components/Canvas/css/canvas.css @@ -0,0 +1,10 @@ +.canvas-main { + height: 100vh; + width: 100vw; + background-color: grey; +} + +* { + padding: 0px; + margin: 0px; +} \ No newline at end of file diff --git a/src/public/src/components/Canvas/index.js b/src/public/src/components/Canvas/index.js new file mode 100644 index 0000000..b827c6f --- /dev/null +++ b/src/public/src/components/Canvas/index.js @@ -0,0 +1,63 @@ +import React, {useState, useLayoutEffect} from 'react'; +const client = require('../../../client.js'); + + + +import './css/canvas.css'; + +const Canvas = props => { + const { angle:a, xPos:x, yPos:y, id } = props; + const [angle, setAngle] = useState(a); + const [xPos, setXPos] = useState(x); + const [yPos,setYPos] = useState(y); + + useLayoutEffect(() => { + },[]) + + useLayoutEffect(() => { + console.log('xPos',xPos, 'yPos',yPos,'angle', angle,"cosine sine values", Math.cos(angle* Math.PI / 180.0),Math.sin(angle* Math.PI / 180.0)); + const car = document.getElementById(id).style; + car.left = `${xPos}px`; + car.top = `${yPos}px`; + car.transform = `rotate(${angle}deg)`; + client.send(JSON.stringify({action:"UPDATE_PLAYER",data:{id,xPos,yPos,angle}})); + },[xPos,yPos,angle]); + + + const handleKeyPress = event => { + + const { keyCode } = event; + + const xDirection = Math.cos(angle* Math.PI / 180.0); + const yDirection = Math.sin(angle* Math.PI / 180.0) + + switch(keyCode) { + case 37: + setAngle(angle-5); + break; + case 38: + setXPos(xPos+5*xDirection); + setYPos(yPos+5*yDirection); + + break; + case 39: + setAngle(angle+5); + break; + case 40: + setXPos(xPos-5*xDirection); + setYPos(yPos-5*yDirection); + break; + default: + + } + + + } + return ( +
+ {props.children} +
+ ); +} + +export default Canvas; \ No newline at end of file diff --git a/src/public/src/components/Car/css/car.css b/src/public/src/components/Car/css/car.css new file mode 100644 index 0000000..aee8927 --- /dev/null +++ b/src/public/src/components/Car/css/car.css @@ -0,0 +1,8 @@ +.car-main { + position: relative; + background: linear-gradient(0.25turn, #3f87a6, #ebf8e1, #f69d3c); + height: 12px; + width: 30px; + transition: .15s; + transform:rotate(0deg); +} \ No newline at end of file diff --git a/src/public/src/components/Car/index.js b/src/public/src/components/Car/index.js new file mode 100644 index 0000000..6ab7220 --- /dev/null +++ b/src/public/src/components/Car/index.js @@ -0,0 +1,18 @@ +import React from 'react'; + +import './css/car.css'; +const Car = props => { + const { id, yPos, xPos,angle } = props; + + + + + return ( +
+ +
+ ); +} + + +export default Car; \ No newline at end of file diff --git a/src/public/src/index.js b/src/public/src/index.js new file mode 100644 index 0000000..d6e602c --- /dev/null +++ b/src/public/src/index.js @@ -0,0 +1,71 @@ +import React from 'react'; +const client = require('../client.js'); +import ReactDOM from 'react-dom'; +import Car from './components/Car'; +import App from './App'; + + +client.onopen = () => { + console.log("Browser connected to the server!!"); + +} + +function isEquivalent(a, b) { + // Create arrays of property names + var aProps = Object.getOwnPropertyNames(a); + var bProps = Object.getOwnPropertyNames(b); + + // If number of properties is different, + // objects are not equivalent + if (aProps.length != bProps.length) { + return false; + } + + for (var i = 0; i < aProps.length; i++) { + var propName = aProps[i]; + + // If values of same property are not equal, + // objects are not equivalent + if (a[propName] !== b[propName]) { + return false; + } + } + + // If we made it this far, objects + // are considered equivalent + return true; +} + + +client.onmessage = (e) => { + // console.log(e) + const {action, data, otherPlayerData} = JSON.parse(e.data); + switch(action){ + case "INIATE": + console.log("iniate") + const otherCars = otherPlayerData.map(other => !isEquivalent(data,other) && ) + ReactDOM.render(,document.getElementById('root')); + break; + case "NEW_PLAYER": + console.log("new-player") + document.dispatchEvent(new CustomEvent('add-player',{ + detail: { + data + } + })); + break; + case "UPDATE_CANVAS": + console.log('update') + const modifiedCar = document.getElementById(data.id); + modifiedCar.style.top = data.yPos; + modifiedCar.style.left = data.xPos; + modifiedCar.style.transform = `rotate(${data.angle}deg)` + break; + default: + console.log(data); + } + +}; + + + \ No newline at end of file diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..fda568c --- /dev/null +++ b/src/server.js @@ -0,0 +1,204 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const WebSocketServer = require('websocket').server; + +const indexPath = path.join(__dirname,'/public/index.html'); +const jsPath = path.join(__dirname,'../dist/bundle.js'); +const cssPath = path.join(__dirname,'../dist/style.css'); + + + + +let carData = []; +let clients = [] + + + + + +const server = http.createServer((req,res) => { + if(req.url==="/"){ + fs.readFile(indexPath, (err, file) => { + if(err) { + res.send(500,{error: err}); + } + res.writeHeader(200, {"Content-Type": "text/html"}); + res.write(file); + res.end() + + }) + } + + if(req.url==="/bundle.js") { + fs.readFile(jsPath, (err, file) => { + if(err) { + console.log("Error getting the javaScript") + } + res.writeHeader(200, {"Content-Type": "text/javascript"}); + res.write(file); + res.end(); + }) + } + + + if(req.url==="/styles.css") { + fs.readFile(cssPath, (err, file ) => { + if(err) { + console.log("Error getting the css") + } + res.writeHeader(200, {"Content-Type": "text/css"}); + res.write(file); + res.end(); + }) + + + } +}); +server.listen(3000,'127.0.0.1'); + + +const broadcastExcept = (action,data,connection) => { + clients.map( socket => { + + if(!isEquivalent(connection,socket)) + socket.send(JSON.stringify( + { + action, + data + })); + }) +} + +const broadcastAll = (action,data) => { + clients.map( socket => { + socket.send(JSON.stringify( + { + action, + data + })); + }) +} + +const updateCars = (newData) => { + return carData.map(car => { + if(car.id === newData.id){ + return newData; + } + + return car; + }) +} + + +const webSocketServer = new WebSocketServer({httpServer:server}); + +webSocketServer.on('request', request => { + const data = { + id: uuid(), + xPos: randomXPos(), + yPos: randomXPos(), + angle: randomAngle() + } + + broadcastAll("NEW_PLAYER",data); + + var connection = request.accept('echo-protocol', request.origin); + + console.log((new Date()) + ' Connection accepted.'); + + + + carData.push(data); + console.log('8888888888888888888888888888888',carData); + connection.send(JSON.stringify( + { + action: 'INIATE', + data, + otherPlayerData:carData + })) + + + // setInterval(() => { + // connection.send(JSON.stringify({ + // action:"test", + // data:data.id + // })) + // }, 1000); + + clients.push(connection); + connection.on('message', message => { + const _message = JSON.parse(message.utf8Data); + switch(_message.action){ + case "UPDATE_PLAYER": + carData = updateCars(_message.data) + console.log("New Canvas Positions", carData); + broadcastExcept("UPDATE_CANVAS",_message.data,connection); + break; + default: + // console.log(_message); + } + + }); + + + connection.on('close', (reasonCode, description) => { + console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.'); + }); +}); + + + + + +const uuid = () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +const randomXPos = () => { + return Math.floor((Math.random() * 200) + 25); +} + +const randomYPos = () => { + return Math.floor((Math.random() * 200) + 25); +} + + + +const randomAngle = () => { + return Math.floor((Math.random() * 360)); +} + +const randomColor = () => { + +} + + +function isEquivalent(a, b) { + // Create arrays of property names + var aProps = Object.getOwnPropertyNames(a); + var bProps = Object.getOwnPropertyNames(b); + + // If number of properties is different, + // objects are not equivalent + if (aProps.length != bProps.length) { + return false; + } + + for (var i = 0; i < aProps.length; i++) { + var propName = aProps[i]; + + // If values of same property are not equal, + // objects are not equivalent + if (a[propName] !== b[propName]) { + return false; + } + } + + // If we made it this far, objects + // are considered equivalent + return true; +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..cc9329e --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,31 @@ +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + + +module.exports = { + mode: "development", + entry: ["./src/public/src/index.js","./src/public/client.js"], + output: { + path: path.resolve(__dirname, "dist"), + filename: "bundle.js", + libraryTarget: "umd" + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: ['babel-loader'] + }, + { + test: /\.css$/, + use: [ 'style-loader', MiniCssExtractPlugin.loader, 'css-loader'] + } + ] + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: 'style.css' + }) + ] +} \ No newline at end of file