diff --git a/traque-back/game.js b/traque-back/game.js index 4a170cf..2011d97 100644 --- a/traque-back/game.js +++ b/traque-back/game.js @@ -1,5 +1,3 @@ -import { Socket } from "socket.io"; - export default class Game { constructor() { this.teams = []; @@ -31,7 +29,8 @@ export default class Game { chased: null, currentLocation: [0, 0], lastSentLocation: [0, 0], - enemyLocation: [0, 0] + enemyLocation: [0, 0], + sockets: [] }); this.updateTeamChasing(); return true; diff --git a/traque-back/index.js b/traque-back/index.js index 581b62a..af0ae89 100644 --- a/traque-back/index.js +++ b/traque-back/index.js @@ -158,7 +158,9 @@ io.of("player").on("connection", (socket) => { socket.on("disconnect", () => { console.log("user disconnected"); - game.getTeam(teamId).sockets = game.getTeam(teamId).sockets.filter(s => s !== socket.id); + if(teamId !== null && game.getTeam(teamId) !== undefined){ + game.getTeam(teamId).sockets = game.getTeam(teamId).sockets.filter(s => s !== socket.id); + } }); socket.on("login", (teamId) => { @@ -175,7 +177,6 @@ io.of("player").on("connection", (socket) => { }); socket.on("send_position", () => { - console.log("send_position", position); game.sendLocation(teamId); game.getTeam(teamId).sockets.forEach(s => { io.of("player").to(s).emit("enemy_position", game.getTeam(teamId).enemyLocation); diff --git a/traque-front/app/page.js b/traque-front/app/page.js deleted file mode 100644 index 2d175e1..0000000 --- a/traque-front/app/page.js +++ /dev/null @@ -1,13 +0,0 @@ -"use client" -import LoginForm from "@/components/team/loginForm"; - -export default function Home() { - function login(teamId) { - console.log(teamId); - } - return ( -
- -
- ); -} diff --git a/traque-front/app/team/layout.js b/traque-front/app/team/layout.js new file mode 100644 index 0000000..f877101 --- /dev/null +++ b/traque-front/app/team/layout.js @@ -0,0 +1,12 @@ +import { TeamConnexionProvider } from "@/context/teamConnexionContext"; +import { TeamProvider } from "@/context/teamContext"; + +export default function AdminLayout({ children}) { + return ( + + + {children} + + + ) +} \ No newline at end of file diff --git a/traque-front/app/team/page.js b/traque-front/app/team/page.js new file mode 100644 index 0000000..141267a --- /dev/null +++ b/traque-front/app/team/page.js @@ -0,0 +1,19 @@ +"use client" +import LoginForm from "@/components/team/loginForm"; +import useGame from "@/hook/useGame"; +import { redirect } from "next/navigation"; +import { useEffect } from "react"; + +export default function Home() { + const { login, loggedIn } = useGame(); + useEffect(() => { + if (loggedIn) { + redirect("/team/track"); + } + }, [loggedIn]); + return ( +
+ login(parseInt(value))}/> +
+ ); +} diff --git a/traque-front/app/track/page.js b/traque-front/app/team/track/page.js similarity index 80% rename from traque-front/app/track/page.js rename to traque-front/app/team/track/page.js index 3d7f9cb..8f6bf3b 100644 --- a/traque-front/app/track/page.js +++ b/traque-front/app/team/track/page.js @@ -1,8 +1,9 @@ "use client"; import Button from '@/components/util/button'; +import useGame from '@/hook/useGame'; import dynamic from 'next/dynamic'; +import { redirect } from 'next/navigation'; import React, { useEffect } from 'react' -import useGame from '../../hook/useGame'; //Load the map without SSR const LiveMap = dynamic(() => import('@/components/team/map'), { @@ -10,7 +11,12 @@ const LiveMap = dynamic(() => import('@/components/team/map'), { }); export default function Track() { - const { currentPosition, enemyPosition, updateCurrentPosition, sendCurrentPosition } = useGame(); + const { currentPosition, enemyPosition, loggedIn, sendCurrentPosition } = useGame(); + useEffect(() => { + if (!loggedIn) { + redirect("/team"); + } + }, [loggedIn]); return ( diff --git a/traque-front/components/team/map.jsx b/traque-front/components/team/map.jsx index 290a234..085167d 100644 --- a/traque-front/components/team/map.jsx +++ b/traque-front/components/team/map.jsx @@ -7,12 +7,14 @@ import "leaflet/dist/leaflet.css"; const DEFAULT_ZOOM = 17; + +// Pan to the center of the map when the position of the user is updated for the first time function MapPan(props) { const map = useMap(); const [initialized, setInitialized] = useState(false); useEffect(() => { - if(!initialized && JSON.stringify(props.center) != "[0,0]") { + if(!initialized && props.center) { map.flyTo(props.center, DEFAULT_ZOOM); setInitialized(true) } @@ -22,20 +24,6 @@ function MapPan(props) { } export default function LiveMap({enemyPosition, currentPosition, ...props}) { - const [positionSet, setPositionSet] = useState(false); - useEffect(() => { - if(!positionSet && JSON.stringify(currentPosition) != "[0,0]") { - setPositionSet(true); - } - }, [currentPosition]); - const [enemyPositionSet, setEnemyPositionSet] = useState(false); - useEffect(() => { - if(!enemyPositionSet && JSON.stringify(enemyPosition) != "[0,0]") { - setEnemyPositionSet(true); - } - }, [enemyPosition]); - - return ( @@ -43,7 +31,7 @@ export default function LiveMap({enemyPosition, currentPosition, ...props}) { attribution='© OpenStreetMap contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> - {positionSet && } - {enemyPositionSet && { const [loggedIn, setLoggedIn] = useState(false); const { adminSocket } = useSocket(); - + function login(password) { adminSocket.emit("login", password); } - - useEffect(() => { - function updateLoginStatus(status) { - setLoggedIn(status); - } - adminSocket.on("login_response", updateLoginStatus); - - return () => { - adminSocket.off("login_response", updateLoginStatus); - }; - }, []); - + + useSocketListener(adminSocket, "login_response", setLoggedIn); + return ( - {children} + {children} ); } @@ -33,5 +25,5 @@ function useAdminConnexion() { return useContext(adminContext); } -export { AdminConnexionProvider, useAdminConnexion}; +export { AdminConnexionProvider, useAdminConnexion }; diff --git a/traque-front/context/adminContext.jsx b/traque-front/context/adminContext.jsx index e6ce9ec..f4af89d 100644 --- a/traque-front/context/adminContext.jsx +++ b/traque-front/context/adminContext.jsx @@ -1,11 +1,26 @@ "use client"; -import { createContext, useContext, useState } from "react"; +import { createContext, useContext, useEffect, useState } from "react"; +import { useSocket } from "./socketContext"; +import { useSocketListener } from "@/hook/useSocketListener"; +import { useAdminConnexion } from "./adminConnexionContext"; const adminContext = createContext(); function AdminProvider({children}) { const [teams, setTeams] = useState([]); const [started, setStarted] = useState(false); + const { adminSocket } = useSocket(); + const {loggedIn} = useAdminConnexion(); + + //Send a request to get the teams when the user logs in + useEffect(() => { + adminSocket.emit("get_teams"); + }, [loggedIn]); + + //Bind listeners to update the team list and the game status on socket message + useSocketListener(adminSocket, "teams", setTeams); + useSocketListener(adminSocket, "game_started", setStarted); + return ( {children} diff --git a/traque-front/context/socketContext.jsx b/traque-front/context/socketContext.jsx index 7c2bd12..0ef0526 100644 --- a/traque-front/context/socketContext.jsx +++ b/traque-front/context/socketContext.jsx @@ -4,16 +4,16 @@ import { createContext, useContext } from "react"; const { io } = require("socket.io-client"); const SOCKET_URL = "http://localhost:3000"; -const USER_SOCKET_URL = SOCKET_URL + "/user"; +const USER_SOCKET_URL = SOCKET_URL + "/player"; const ADMIN_SOCKET_URL = SOCKET_URL + "/admin"; -export const userSocket = io(USER_SOCKET_URL); +export const teamSocket = io(USER_SOCKET_URL); export const adminSocket = io(ADMIN_SOCKET_URL); export const SocketContext = createContext(); export default function SocketProvider({ children }) { return ( - {children} + {children} ); } diff --git a/traque-front/context/teamConnexionContext.jsx b/traque-front/context/teamConnexionContext.jsx new file mode 100644 index 0000000..0aec041 --- /dev/null +++ b/traque-front/context/teamConnexionContext.jsx @@ -0,0 +1,31 @@ +"use client"; +import { createContext, useContext, useState } from "react"; +import { useSocket } from "./socketContext"; +import { useSocketListener } from "@/hook/useSocketListener"; + +const teamConnexionContext = createContext(); +const TeamConnexionProvider = ({ children }) => { + const [loggedIn, setLoggedIn] = useState(false); + const [teamId, setTeamId] = useState(null); + const { teamSocket } = useSocket(); + + function login(id) { + teamSocket.emit("login", id); + setTeamId(id); + } + + useSocketListener(teamSocket, "login_response", setLoggedIn); + + return ( + + {children} + + ); +} + +function useTeamConnexion() { + return useContext(teamConnexionContext); +} + +export { TeamConnexionProvider, useTeamConnexion}; + diff --git a/traque-front/context/teamContext.jsx b/traque-front/context/teamContext.jsx new file mode 100644 index 0000000..b3eca3f --- /dev/null +++ b/traque-front/context/teamContext.jsx @@ -0,0 +1,36 @@ +"use client"; +import { useLocation } from "@/hook/useLocation"; +import { useSocketListener } from "@/hook/useSocketListener"; +import { createContext, useContext, useEffect, useState } from "react"; +import { useSocket } from "./socketContext"; +import { useTeamConnexion } from "./teamConnexionContext"; + +const teamContext = createContext() +function TeamProvider({children}) { + const [enemyPosition, setEnemyPosition] = useState(); + const currentPosition = useLocation(10000); + const {teamSocket} = useSocket(); + const {loggedIn} = useTeamConnexion(); + + useSocketListener(teamSocket, "enemy_position", setEnemyPosition); + + //Send the current position to the server when the user is logged in + useEffect(() => { + console.log("sending position", currentPosition); + if(loggedIn) { + teamSocket.emit("update_position", currentPosition); + } + }, [loggedIn, currentPosition]); + + return ( + + {children} + + ); +} + +function useTeamContext() { + return useContext(teamContext); +} + +export { TeamProvider, useTeamContext }; \ No newline at end of file diff --git a/traque-front/hook/useAdmin.jsx b/traque-front/hook/useAdmin.jsx index 077fe30..45e9ede 100644 --- a/traque-front/hook/useAdmin.jsx +++ b/traque-front/hook/useAdmin.jsx @@ -1,28 +1,14 @@ import { useAdminContext } from "@/context/adminContext"; import { useSocket } from "@/context/socketContext"; -import { Underdog } from "next/font/google"; - -const { useEffect, useState } = require("react"); export default function useAdmin(){ - const {teams, setTeams, started, setStarted} = useAdminContext(); + const {teams, started} = useAdminContext(); const {adminSocket} = useSocket(); function pollTeams() { adminSocket.emit("get_teams"); } - useEffect(() => { - pollTeams(); - }, []); - useEffect(() => { - adminSocket.emit("get_teams"); - adminSocket.on("teams", setTeams); - return () => { - adminSocket.off("teams", setTeams); - } - }, []); - function getTeam(teamId) { return teams.find(team => team.id === teamId); } @@ -56,13 +42,6 @@ export default function useAdmin(){ adminSocket.emit("stop_game"); } - useState(() => { - adminSocket.on("game_started", setStarted); - return () => { - adminSocket.off("game_started", setStarted); - } - }, []); - return { teams, started, pollTeams, getTeam, getTeamName, reorderTeams, addTeam, removeTeam, startGame, stopGame, setTeamName }; } \ No newline at end of file diff --git a/traque-front/hook/useGame.jsx b/traque-front/hook/useGame.jsx index d422bf5..67fe9ef 100644 --- a/traque-front/hook/useGame.jsx +++ b/traque-front/hook/useGame.jsx @@ -1,61 +1,17 @@ "use client"; import { useSocket } from "@/context/socketContext"; -import { useEffect, useState } from "react"; +import { useTeamConnexion } from "@/context/teamConnexionContext"; +import { useTeamContext } from "@/context/teamContext"; export default function useGame() { - const {userSocket} = useSocket(); - const [loggedIn, setLoggedIn] = useState(false); - const [teamId, setTeamId] = useState(null); - const [enemyPosition, setEnemyPosition] = useState([0, 0]); - const [currentPosition, setCurrentPosition] = useState([0, 0]); - - function updateCurrentPosition(position) { - setCurrentPosition(position); - userSocket.emit("update_position", position); - } + const {teamSocket} = useSocket(); + const {loggedIn, login, teamId} = useTeamConnexion(); + const {currentPosition, enemyPosition} = useTeamContext(); function sendCurrentPosition() { - userSocket.emit("send_position", currentPosition); + teamSocket.emit("send_position", currentPosition); } - useEffect(() => { - function updateEnemyPosition(position) { - setEnemyPosition(position); - } - userSocket.on("enemy_position", updateEnemyPosition); - return () => { - userSocket.off("enemy_position", updateEnemyPosition); - } - }, []); - - function login(teamId) { - setTeamId(teamId); - userSocket.emit("login", teamId); - } - useEffect(() => { - function updateLoginStatus(status) { - setLoggedIn(status); - } - userSocket.on("login_reponse", updateLoginStatus); - - return () => { - userSocket.off("login_response", updateLoginStatus); - } - }, []); - - useEffect(() => { - function udpate() { - console.log("update") - const position = navigator.geolocation.getCurrentPosition((position) => { - updateCurrentPosition([position.coords.latitude, position.coords.longitude]); - }, () => { }, { enableHighAccuracy: true, timeout: Infinity, maximumAge: 0 }); - } - setInterval(udpate, 1000); - return () => { - clearInterval(udpate); - } - }, []); - - return { updateCurrentPosition, sendCurrentPosition, login, enemyPosition, currentPosition, loggedIn, teamId }; + return { sendCurrentPosition, login, enemyPosition, currentPosition, loggedIn, teamId }; } \ No newline at end of file diff --git a/traque-front/hook/useLocation.jsx b/traque-front/hook/useLocation.jsx new file mode 100644 index 0000000..70e8c17 --- /dev/null +++ b/traque-front/hook/useLocation.jsx @@ -0,0 +1,21 @@ +"use client"; +import { useEffect, useState } from "react"; + +/** + * A hook that returns the location of the user and updates it periodically + * @returns {Object} The location of the user + */ +export function useLocation(interval) { + const [location, setLocation] = useState(); + useEffect(() => { + function update() { + navigator.geolocation.getCurrentPosition((position) => { + setLocation([position.coords.latitude, position.coords.longitude]); + setTimeout(update, interval); + }, () => { }, { enableHighAccuracy: true, timeout: Infinity, maximumAge: 0 }); + } + update(); + }, []); + + return location; +} \ No newline at end of file diff --git a/traque-front/hook/useSocketListener.jsx b/traque-front/hook/useSocketListener.jsx new file mode 100644 index 0000000..85c89e8 --- /dev/null +++ b/traque-front/hook/useSocketListener.jsx @@ -0,0 +1,10 @@ +import { useState } from "react"; + +export function useSocketListener(socket, event, callback) { + return useState(() => { + socket.on(event, callback); + return () => { + socket.off(event, callback); + } + }, []); +} \ No newline at end of file