diff --git a/backend/index.mjs b/backend/index.mjs deleted file mode 100644 index b31b947..0000000 --- a/backend/index.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import express from "express"; -import socketIO from "socket.io"; - -const PORT = 3001; - -const app = express(); - -app.get('/', (req, res) => res.send('Hello World!')); - -const server = app.listen(PORT, () => console.log(`Example app listening on port ${PORT}!`)); -const io = socketIO(server); - -io.on('connection', socket => { - console.log('a user connected', socket.id); - - setInterval(() => { - console.log('send :)') - socket.emit("SENDING_NEW_TIME", "ASD") - }, 1500) -}); diff --git a/backend/package.json b/backend/package.json index 6d2108c..d2ece0b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,13 +2,12 @@ "name": "centurion-via-backend", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "./src/index.mjs", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", - "type": "module", "dependencies": { "express": "^4.17.1", "socket.io": "^2.3.0" diff --git a/backend/src/Lobby.js b/backend/src/Lobby.js new file mode 100644 index 0000000..427d9fc --- /dev/null +++ b/backend/src/Lobby.js @@ -0,0 +1,109 @@ +const User = require("./User.js"); + +module.exports = class Lobby { + /** + * @type {User[]} + */ + users = []; + /** + * @type {string|undefined} + */ + leaderId = undefined; + + running = false; + runningInterval = undefined; + currentTime = 0; + + constructor(name) { + this.name = name; + } + + run() { + this.running = true; + this.runningInterval = setInterval(() => { + this.currentTime += 1; + }, 1000); + } + + pause() { + this.running = false; + clearInterval(this.runningInterval); + } + + /** + * + * @returns {boolean} + */ + hasUsers() { + return this.users.length !== 0; + } + + + setRandomLeader() { + if (this.hasUsers()) { + this.leaderId = this.users[0].id; + } + } + + /** + * + * @param {User} user + */ + addUser(user) { + this.users.push(user); + } + + /** + * + * @param id + * @returns {User|undefined} + */ + getUser(id) { + return this.users.find(u => u.id === id); + } + + /** + * + * @param {string} id + */ + removeUser(id) { + this.users = this.users.filter(u => u.id !== id); + } + + /** + * + * @returns {boolean} + */ + hasLeader() { + return !!this.leaderId; + } + + /** + * + * @param {string} id + * @returns {boolean} + */ + isLeader(id) { + return this.leaderId === id; + } + + /** + * + * @param {string} id + */ + setLeader(id) { + if (!this.getUser(id)) { + throw new Error('user_not_in_lobby'); + } + + this.leaderId = id; + } + + /** + * + * @returns {User|undefined} + */ + getLeader() { + return this.users.find(u => u.id === this.leaderId) + } +}; diff --git a/backend/src/User.js b/backend/src/User.js new file mode 100644 index 0000000..b071f72 --- /dev/null +++ b/backend/src/User.js @@ -0,0 +1,5 @@ +module.exports = class User { + constructor(id) { + this.id = id; + } +}; \ No newline at end of file diff --git a/backend/src/index.js b/backend/src/index.js new file mode 100644 index 0000000..b297585 --- /dev/null +++ b/backend/src/index.js @@ -0,0 +1,44 @@ +const express = require("express"); +const socketIO = require("socket.io"); + +const service = require("./service.js"); +const state = require("./state.js"); + +const PORT = 3001; + +const app = express(); +const server = app.listen(PORT, () => console.log(`Example app listening on port ${PORT}!`)); +const io = socketIO(server); + +app.get('/', (req, res) => res.send('
' + JSON.stringify(state) + '
')); + +io.on('connection', socket => { + const socketId = socket.id; + + console.log('a user connected', socketId); + + const lobby = service.joinLobby(socketId); + socket.join(lobby.name); + socket.emit('welcome', {lobby: lobby.name}); + + + socket.on('disconnect', (reason) => { + console.log('Disconnected:', socketId); + service.leaveLobby(socketId); + }); + + socket.on('join_lobby', (lobbyId, callback) => { + console.log(`${socketId} wants to join '${lobbyId}'.`); + + // Leave current lobby first + service.leaveLobby(socketId); + const lobby = service.joinLobby(socketId, lobbyId); + + socket.join(lobby.name); + + callback(null, { + status: 'ok', + lobby: lobby.name + }); + }); +}); \ No newline at end of file diff --git a/backend/src/service.js b/backend/src/service.js new file mode 100644 index 0000000..1afea33 --- /dev/null +++ b/backend/src/service.js @@ -0,0 +1,43 @@ +const User = require("./User.js"); +const state = require("./state.js"); + +/** + * + * @param {string} socketId + * @param {number|undefined} lobbyId + * @returns {Lobby} + */ +function joinLobby(socketId, lobbyId=undefined) { + let lobby = state.getLobby(lobbyId); + if (!lobby) { + lobby = state.createRandomLobby(); + } + + lobby.addUser(new User(socketId)); + + if (!lobby.hasLeader()) { + lobby.setLeader(socketId); + } + + state.lobbies[lobby.name] = lobby; + return lobby; +} + +function leaveLobby(socketId) { + Object.keys(state.lobbies).forEach(lobbyId => { + const lobby = state.getLobby(lobbyId); + + lobby.removeUser(socketId); + + if (!lobby.hasUsers()) { + state.removeLobby(lobbyId); + return; + } + + if (lobby.getLeader() === socketId) { + lobby.setRandomLeader(); + } + }); +} + +module.exports = {joinLobby, leaveLobby}; \ No newline at end of file diff --git a/backend/src/state.js b/backend/src/state.js new file mode 100644 index 0000000..a1ba849 --- /dev/null +++ b/backend/src/state.js @@ -0,0 +1,62 @@ +const Lobby = require("./Lobby.js"); +const User = require("./User.js"); +const {getRandomInt} = require("./util.js"); + +class State { + /** + * @type {Object.} + */ + lobbies = {}; + lobbyCount = 0; + + constructor() { + } + + /** + * @returns {Lobby} + */ + createRandomLobby() { + let lobby = undefined; + + while (!lobby) { + const id = getRandomInt(100, Math.max(1000, this.lobbyCount * 2)); + lobby = this.createLobby(id); + } + + return lobby; + } + + /** + * + * @param lobbyId + * @returns {Lobby|undefined} + */ + getLobby(lobbyId) { + if (!lobbyId || !this.lobbies.hasOwnProperty(lobbyId)) { + return undefined; + } + + return this.lobbies[lobbyId]; + } + + /** + * Returns undefined when the lobby already exists. + * @param {number} lobbyId + * @returns {Lobby|undefined} + */ + createLobby(lobbyId) { + if (this.lobbies.hasOwnProperty(lobbyId)) { + return undefined; + } + + this.lobbyCount += 1; + return new Lobby(lobbyId); + } + + removeLobby(lobbyId) { + this.lobbyCount -= 1; + delete this.lobbies[lobbyId]; + } +} + +module.exports = new State(); diff --git a/backend/src/util.js b/backend/src/util.js new file mode 100644 index 0000000..85c0c89 --- /dev/null +++ b/backend/src/util.js @@ -0,0 +1,15 @@ +/** + * Generates random int + * @param {number} min, inclusive + * @param {number} max, exclusive + * @returns {number} + */ +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min)) + min; +} + +module.exports = { + getRandomInt +}; \ No newline at end of file diff --git a/backend/test/a.js b/backend/test/a.js new file mode 100644 index 0000000..0a667ea --- /dev/null +++ b/backend/test/a.js @@ -0,0 +1,15 @@ +const express = require('express'); +const state = require('./state.js'); +const b = require('./b.js'); + +const PORT = 3002; + +const app = express(); +const server = app.listen(PORT, () => console.log(`Example app listening on port ${PORT}!`)); + +app.get('/', (req, res) => { + let i = state.a; + b.test(i += 1); + + return res.send('
' + JSON.stringify(state) + '
') +}); diff --git a/backend/test/b.js b/backend/test/b.js new file mode 100644 index 0000000..0a199df --- /dev/null +++ b/backend/test/b.js @@ -0,0 +1,7 @@ +const state = require('./state.js'); + +module.exports = { + test: (i) => { + state.setA(i); + } +}; \ No newline at end of file diff --git a/backend/test/state.js b/backend/test/state.js new file mode 100644 index 0000000..8e75986 --- /dev/null +++ b/backend/test/state.js @@ -0,0 +1,12 @@ +class State { + a = 3; + constructor() { + + } + + setA(i) { + this.a = i; + } +} + +module.exports = new State(); \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8b7e2aa..0dec889 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,36 +2,56 @@ import React, {useEffect, useState} from 'react'; import './App.css'; import logo from "./via-logo.svg" import {Row} from "antd" -import socket from "./util/socket"; +import socket, { emit } from "./util/socket"; import NextShot from "./components/NextShot"; import ShotsTaken from "./components/ShotsTaken"; import Feed from "./components/Feed"; +import Lobby from "./components/Lobby"; const App = () => { + const [connected, setConnected] = useState(false); + const [lobbyId, setLobbyId] = useState(null); useEffect(() => { - socket.on('connected', () => { - console.log('connected :)') + socket.on('welcome', async (obj: any) => { + const {lobby} = obj; + setLobbyId(lobby); + setConnected(true); + + // const joined = await emit('join_lobby', 123); }); return () => { socket.off("SENDING_NEW_TIME"); - socket.off("connection"); + socket.off("welcome"); } }); + const gameContent = ( + + + + + + ); + + const lobbyContent = ( + + + + ); + + const content = lobbyContent; + return ( <>
- - - - - + {content}
); diff --git a/frontend/src/components/Lobby.tsx b/frontend/src/components/Lobby.tsx new file mode 100644 index 0000000..fc75031 --- /dev/null +++ b/frontend/src/components/Lobby.tsx @@ -0,0 +1,25 @@ +import React, {useEffect} from 'react'; +import {Col, Progress} from "antd" +import socket from "../util/socket"; + + +const Lobby = () => { + useEffect(() => { + socket.on("SENDING_NEW_TIME", (data: string) => { + + }); + + return () => { + socket.off("SENDING_NEW_TIME"); + } + }); + + return ( + +

Centurion!

+ + + ); +}; + +export default Lobby; diff --git a/frontend/src/util/socket.ts b/frontend/src/util/socket.ts index ce7b937..9078d7c 100644 --- a/frontend/src/util/socket.ts +++ b/frontend/src/util/socket.ts @@ -1,3 +1,23 @@ import io from "socket.io-client"; -export default io("http://localhost:3001"); + +const socket = io("http://localhost:3001"); +export default socket; + +/** + * Promisify emit. + * @param event + * @param arg + * @param callback + */ +export function emit(event: string, arg: any) { + return new Promise((resolve, reject) => { + socket.emit(event, arg, (err: any, res: any) => { + if (err) { + return reject(err); + } + + resolve(res); + }) + }) +} \ No newline at end of file